외부 API가 404나 500을 돌려줬는데 MCP 도구는 성공했다고 말한다면, 먼저 fetch 뒤에서 HTTP 상태를 확인하는지 보자. try/catch가 있다는 이유만으로 HTTP 실패까지 걸러지는 것은 아니다. 응답을 받은 것, 본문을 읽은 것, 요청한 일이 끝난 것은 서로 다른 단계다.
이 글은 JavaScript fetch를 사용하는 도구 구현의 진단 안내다. MCP 서버 자체가 반환한 HTTP 상태와 도구 안에서 호출한 외부 API의 HTTP 상태를 구분한다. MCP SDK나 프로토콜의 특정 버전으로 이전하는 방법은 아니다.
먼저 네 단계를 나눈다
1. 요청 단계: fetch가 Response를 반환했는가, 아니면 예외가 발생했는가.
2. HTTP 단계: response.status와 response.ok는 무엇인가.
3. 본문 단계: JSON을 읽을 수 있는가. 필요한 필드의 형식이 맞는가.
4. 업무 단계: 그 API 계약에서 이 상태와 본문이 무엇을 뜻하는가.
예를 들어 조회 도구에서 404는 주소 오타일 수도 있고, 정상적인 ‘조회 대상 없음’일 수도 있다. 어느 쪽인지는 해당 API 계약으로 결정해야 한다. 무조건 성공이나 재시도로 바꿀 수 없다. 202도 접수 사실과 처리 완료를 구분해야 한다. 작업 완료가 필요한 도구라면 작업 조회 경로를 별도로 확인한다.
catch에 잡히지 않는 이유
Fetch 표준에서 Response.ok는 상태가 200~299일 때 참이다. HTTP 404·500 응답을 받았다는 이유만으로 fetch의 Promise가 거부되지는 않는다. 따라서 다음 코드에서는 오류 JSON도 정상 결과처럼 다음 단계로 넘어갈 수 있다.
const response = await fetch(url);
const data = await response.json();
return data; // HTTP 상태와 업무 결과를 아직 확인하지 않았다.
반대로 204는 ok가 참이지만 본문이 없다. 모든 정상 응답에 json()을 호출하는 구현은 이 경우 파싱 오류를 만들 수 있다. 이 차이는 Fetch 표준의 상태 정의, Response.ok, fetch 처리 절차, json() 처리 절차와 대조했다.
실제로 확인한 범위
2026년 10월 20일 한국 시간, Node.js v24.19.0에서 임시 로컬 HTTP 서버를 만들고 네 가지 합성 응답을 순서대로 호출했다. 외부 서비스나 실제 MCP 제품의 장애를 재현한 시험이 아니다. 서버는 200·404·500에는 합성 JSON을, 204에는 빈 본문을 반환했다.
| 합성 응답 | fetch 결과 | ok | json() 결과 |
|---|---|---|---|
| 200 | Response 반환 | true | 합성 객체 |
| 404 | Response 반환 | false | 합성 오류 객체 |
| 500 | Response 반환 | false | 합성 오류 객체 |
| 204 | Response 반환 | true | SyntaxError |
상태 코드와 ok 값에 대한 검사를 통과했다. 이 시험이 확인한 것은 HTTP 응답과 JSON 읽기의 경계다. DNS 실패, TLS 오류, 실제 취소, 대용량 응답, 재시도, MCP 호스트의 오류 표시까지 시험한 것은 아니다. Node.js 공식 문서는 내장 fetch를 설명한다. 문서의 현재 배포 버전과 시험에 쓴 런타임 버전은 같다고 가정하지 않는다.
복사해서 확인하는 작은 검사
다음 예제는 네트워크 요청 없이 Response 객체만으로 분기 조건을 확인한다. 위의 실제 로컬 HTTP 시험과 달리 fetch 자체를 실행하는 코드는 아니다. 실제 구현에서는 서버에서 허용한 API 주소를 사용하고, 사용자가 입력한 임의 URL을 그대로 요청하지 않는다.
// Node.js 내장 Response 사용. 외부 호출과 설치가 없다.
async function readLookup(response) {
if (response.status === 204) {
return { kind: 'empty' }; // 이 조회 API가 204를 허용할 때만
}
if (!response.ok) {
await response.body?.cancel();
return { kind: 'http-error', status: response.status };
}
if (response.status !== 200) {
await response.body?.cancel();
return { kind: 'unexpected-status', status: response.status };
}
let data;
try {
data = await response.json();
} catch {
return { kind: 'body-error', status: response.status };
}
if (!data || typeof data.id !== 'string') {
return { kind: 'schema-error', status: response.status };
}
return { kind: 'found', id: data.id };
}
for (const status of [200, 404, 500, 204]) {
const body = status === 204 ? null : JSON.stringify({ id: 'sample-1' });
console.log(status, await readLookup(new Response(body, { status })));
}
예상 분기는 200에서 found, 404·500에서 http-error, 204에서 empty다. 이것은 200의 문자열 id 또는 204의 빈 결과만 허용하는 가상 조회 계약의 예시다. 202처럼 그 밖의 2xx는 unexpected-status로 분리한다. 모든 API를 처리하는 공통 라이브러리나 MCP 결과 형식이 아니다. 204를 허용하지 않는 조회 계약이라면 empty 대신 계약 위반으로 처리한다. id의 의미와 존재 여부도 실제 계약에 맞춰 더 검증해야 한다.
운영 코드에는 응답 크기 제한, 대기 시간과 취소 처리, Content-Type 확인, 리디렉션 정책, 허용된 목적지 검증이 추가로 필요하다. json() 성공은 JSON 구문이 맞다는 뜻일 뿐이며 본문 크기 제한도 대신하지 않는다. 오류 본문에는 인증 정보나 고객 데이터가 섞일 수 있으므로 원문 전체를 모델이나 공개 로그에 넘기지 않는다.
증상별 다음 확인
– 404인데 도구 성공: 외부 API 상태를 버린 위치를 찾는다. 주소·메서드·대상 식별자와 ‘없음’의 계약을 확인한다.
– 500인데 정상 데이터처럼 처리: HTTP 실패 분기를 찾는다. 쓰기 작업이었다면 재호출 전에 반영 여부를 조회한다.
– 204에서 JSON 오류: 빈 본문이 정상인 작업인지 확인하고 파싱 전 분기한다.
– 200인데 필요한 값이 없음: 스키마와 업무 완료 조건을 검사한다. 정상 HTTP 상태만으로 완료 문구를 만들지 않는다.
– fetch 자체가 예외: HTTP 오류 응답을 받은 경우와 분리해 원인을 조사한다. 예외 이름만으로 특정 네트워크 원인을 단정하지 않는다.
MCP 경계에서 버리지 말아야 할 정보
앞의 kind는 내부 분류 예시다. 이를 MCP 결과에 그대로 끼워 넣는 규칙은 없다. 사용하는 SDK와 도구 계약에 맞춰 성공 데이터, 대상 없음, 실행 실패를 구분하고, 클라이언트가 실패를 성공 문장으로 읽지 않도록 결과를 확인한다. 특히 외부 요청의 실패를 빈 배열로 덮으면 ‘조회 성공, 결과 0건’과 구별하기 어려워진다.
기록은 호출 구간, 상태 코드, 파싱 여부, 업무 판정 정도로 시작한다. 실제로 제공된 요청 식별자가 있으면 비밀정보를 제거한 뒤 연결하되, 없는 식별자를 만들지 않는다. 쓰기 요청은 서버가 반영한 뒤 응답만 잃을 수도 있으므로 이 글의 분기만으로 자동 재시도를 추가하지 않는다.
오류를 MCP 결과에 어떻게 표시할지는 isError와 JSON-RPC error 구분을, 상태를 알 수 없는 쓰기의 재시도는 결과부터 확인하는 중복 실행 방지를 함께 참고한다. 두 문제 모두 ‘응답을 받았다’와 ‘일이 끝났다’를 나누는 데서 시작한다.
적용 범위와 확인일
– 확인일: 2026년 10월 20일, Asia/Seoul.
– 직접 시험: Node.js v24.19.0 내장 fetch, 로컬 합성 HTTP 응답 4개.
– 공식 대조: WHATWG Fetch Living Standard와 Node.js globals 문서. 특정 MCP SDK 버전은 사용하거나 시험하지 않았다.
– 한계: 실제 MCP 서버·호스트·외부 API의 장애 해결을 보증하지 않는다. 운영 적용 전에는 사용 중인 API의 상태 코드와 응답 계약을 먼저 확인한다.