MCP 호출은 응답했는데 작업이 실패했을 때: isError와 JSON-RPC error 구분

MCP 응답을 받았어도 도구가 하려던 일이 끝났는지는 따로 확인해야 한다. 먼저 JSON-RPC 최상위의 error가 있는지 보고, 정상적인 result 안에서는 resultType과 isError를 확인한다. 마지막으로 반환값과 실제 대상 상태를 대조한다. 이 순서를 건너뛰면 “응답이 왔다”를 “저장이 끝났다”로 잘못 기록할 수 있다.

이 글은 2026년 10월 8일 확인한 MCP 2026-07-28 규격을 기준으로 응답을 읽는 방법을 설명한다. 아래 JSON은 구조를 비교하기 위해 만든 가상 예시다. 특정 서비스의 장애를 재현하거나 실제 저장 성공을 검증한 기록은 아니다. 구버전 SDK는 응답 형식과 기능 지원이 다를 수 있다.

어디서 실패했는지 먼저 나눈다

로그에 같은 “오류”라는 말이 있어도 확인할 위치는 다르다. 원격 HTTP 서버의 응답 상태, MCP 메시지 자체의 거절, 도구 내부의 업무 실패를 한 칸에 모으지 않는다. MCP Tools의 오류 처리 절도 프로토콜 오류와 도구 실행 오류를 구분한다.

관측한 것먼저 확인할 범위
연결이 끊기거나 응답을 받지 못함전송 경로와 시간 제한. 서버에서 작업이 실행됐는지는 아직 모른다.
최상위 error요청 형식·메서드·도구 이름·필수 메타데이터와 오류 코드
result.isError: true도구가 돌려준 입력 검증·업무 조건·외부 API 실패 설명
resultType: input_required추가 입력 요청. 작업 완료로 기록하지 않는다.
resultType: complete, 오류 표시 없음결과 내용·출력 스키마·대상 상태. 완료 응답 자체가 원하는 업무 결과를 보장하지는 않는다.

예를 들어 도구 이름이 틀렸다면 사용 가능한 이름과 호출 인자를 대조한다. 날짜 범위를 처리하는 도구가 “시작일이 종료일보다 늦다”고 반환했다면 날짜의 의미를 고쳐야 한다. 두 상황에 모두 연결 재시작을 적용해도 잘못된 값은 남는다.

최상위 error와 result 안의 isError를 비교한다

첫 번째는 존재하지 않는 도구 이름을 보냈다는 가상 프로토콜 오류다. JSON-RPC 응답에는 result와 error가 동시에 들어갈 수 없다. id를 요청과 맞추고, 오류의 code·message·필요한 data를 읽는다. 오류 메시지에 비밀값이나 개인 정보가 있으면 외부 공유 전에 제거한다.

{
  "jsonrpc": "2.0",
  "id": 21,
  "error": {
    "code": -32602,
    "message": "Unknown tool: list_invoices_typo"
  }
}

두 번째는 도구 호출 결과 안에 업무 실패가 담긴 가상 예시다. 응답을 파싱할 수 있고 result도 있지만 isError가 true다. 여기서 resultType: complete는 이 결과 응답이 완료됐다는 뜻으로 읽어야 한다. 요청한 날짜 범위의 조회가 성공했다는 뜻으로 읽으면 안 된다.

{
  "jsonrpc": "2.0",
  "id": 22,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "시작일은 종료일보다 늦을 수 없습니다. 날짜 범위를 확인하세요."
      }
    ],
    "isError": true
  }
}

JSON-RPC는 메시지의 성공·오류 구조를 정의하고, MCP 도구 결과는 그 안에 실행 오류를 표현한다. 바깥 구조를 처리한 뒤 안쪽 결과까지 읽어야 하는 이유다. 이 구분은 JSON-RPC 응답 규칙과 MCP의 도구 결과 규칙을 함께 보면 분명해진다.

로그를 읽는 순서를 고정한다

  • 요청과 응답을 연결한다. 요청 시각, 전송 방식, 프로토콜 버전, 도구 이름과 요청 ID를 대조한다. 실제 요청 ID가 없으면 임의로 만들지 않는다.
  • 메시지를 정상적으로 받았는지 본다. 타임아웃·끊김과 서버가 보낸 오류 응답을 구분한다. 로컬 SDK가 만든 오류를 서버의 원문으로 적지 않는다.
  • 최상위 error를 먼저 처리한다. 있으면 code와 message를 보존하고, 요청 형식·도구 이름 등 해당 문제를 좁힌다. 결과 객체가 있다는 가정으로 아래 필드를 읽지 않는다.
  • result의 단계와 실패 표시를 읽는다. input_required이면 필요한 사용자 입력 흐름을 확인한다. complete이면서 isError가 true이면 도구 실행 실패로 기록한다. 알 수 없는 resultType은 성공으로 넘기지 않는다.
  • 최종 산출물을 확인한다. 읽기 도구라면 실제 반환 데이터와 범위, 쓰기 도구라면 대상의 현재 상태를 확인한다. 출력 스키마가 있으면 별도로 검증한다.

2026-07-28 규격에서 필수 요청 메타데이터가 빠진 경우도 확인 대상이다. _meta의 프로토콜 버전과 클라이언트 capability 필드는 매 요청에 필요하다. 다만 모든 -32602 오류를 이 문제로 단정해서는 안 된다. 같은 코드는 다른 잘못된 매개변수에도 쓰인다. 실제 메시지와 해당 버전의 메타데이터 요구사항을 비교한다.

복사해서 쓰는 최소 진단 기록

다음은 지원 요청이나 회귀 점검에 쓸 수 있는 기록 틀이다. 원문 전체를 무조건 붙이지 않고, 오류를 구분하는 데 필요한 값만 남긴다. 입력·출력에 토큰, 고객 내용, 개인 경로가 섞여 있다면 공개용 기록에는 넣지 않는다.

확인 시각 / 시간대:
클라이언트·서버·SDK 버전:
MCP 프로토콜 버전 / 전송 방식:
도구 이름 / 실제 요청 ID:
응답 수신 여부 / 로컬 시간 제한 발생 여부:
최상위 error.code / 민감정보를 제거한 message:
resultType / isError:
기대했던 대상 상태:
읽기로 확인한 현재 상태:
변경한 조건 1개 / 재확인 결과:

“호출 실패” 한 줄로 남기는 대신 “응답은 받음, resultType은 complete, isError는 true, 날짜 범위 오류, 대상 변경은 없음”처럼 관측한 층위를 적는다. 반대로 대상 변경 여부를 조회하지 못했다면 “변경 없음”이 아니라 “확인하지 못함”으로 남긴다. 이 차이가 재시도 판단에 직접 영향을 준다.

재시도 전에 이미 반영됐는지 확인한다

타임아웃은 결과를 받지 못했다는 관측이다. 서버가 아무 일도 하지 않았다는 증거는 아니다. 특히 생성·결제·발송처럼 중복 실행에 비용이 생기는 도구는 같은 요청을 곧바로 반복하기보다, 가능한 읽기 경로로 대상 상태를 확인하는 편이 안전하다. 이는 이 글의 운영상 권고이며 모든 MCP 서버가 제공하는 중복 방지 기능은 아니다.

서버가 별도의 작업 ID나 멱등성 키를 제공한다면 그 서비스 문서에 정의된 범위에서 사용한다. JSON-RPC의 id는 요청과 응답을 연결하는 값이므로, 같은 값을 다시 보냈다는 이유만으로 중복 실행이 방지된다고 가정하지 않는다. 사용자 승인이나 인증이 필요한 상태라면 그 절차를 해결해야 하며 재시도 횟수를 늘려 우회하지 않는다.

도구가 실패 사유를 알려줬다면 입력이나 선행 조건 중 무엇을 바꿨는지 먼저 적는다. 아무 조건도 바꾸지 않은 반복 호출은 새로운 진단 정보를 거의 주지 않을 수 있다. 수정 뒤에는 오류 표시가 사라졌는지와 원하는 대상 상태가 만들어졌는지를 각각 확인한다.

이 글의 확인 범위와 다음 점검

확인한 것은 공식 문서의 오류 구조와 그 구조를 읽는 절차다. 실제 서비스의 승인·인증 UI, 특정 SDK의 자동 재시도, 변경의 원자성·롤백, 출력값의 사실성은 검증하지 않았다. 코드가 예외를 던지지 않았다는 사실만으로도 성공을 선언하지 않는다. 성공 기준은 도구의 결과 계약과 사용자가 기대한 대상 상태까지 포함해 정한다.

공식 자료

확인일: 2026년 10월 8일. 예시는 설명용 가상 데이터이며 실제 장애·수정 결과를 나타내지 않는다.


다른 글 보기 · 주제 탐색 · 작성자와 편집 기준 · 문의·정정 요청

RUDA DIRECTOR에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기