MCP 호출이 415로 거절될 때: Content-Type·압축·Accept 구분하기

MCP 호출이 HTTP 415로 거절됐다면 도구 인자부터 고치지 말고 서버에 실제로 보낸 본문의 형식과 압축 상태를 확인한다. JSON을 만들었다는 사실과 JSON으로 전송했다는 사실은 다르다. 요청의 Content-Type, Content-Encoding, 본문 바이트를 나란히 놓아야 원인을 좁힐 수 있다.

이 글은 HTTP 요청을 직접 만들거나 게이트웨이를 운영하는 개발자를 위한 진단 절차다. MCP 규격 2026-07-28의 Streamable HTTP와 RFC 9110을 2026년 10월 16일 한국시간 기준으로 대조했다. 특정 SDK 버전을 시험한 글이 아니며, 아래 예시는 가상 관측 기록이다. 실제 장애 재현이나 수정 성공을 주장하지 않는다.

415에서 먼저 구분할 세 가지

RFC 9110 §15.5.16은 415를 대상 리소스의 해당 메서드가 요청 콘텐츠 형식을 지원하지 않을 때의 거절로 설명한다. 원인은 표시한 미디어 유형, 콘텐츠 코딩 또는 실제 데이터 검사에 있을 수 있다. 따라서 응답 코드 하나로 “JSON 문법 오류”라고 단정할 수 없다. RFC 9110 원문

확인 항목묻는 질문
Content-Type지금 보내는 본문을 어떤 형식이라고 설명했는가?
Content-Encoding본문에 gzip 같은 변환을 실제 적용했는가?
요청의 Accept응답으로 어떤 형식을 받을 수 있다고 알렸는가?

요청에 Accept: application/json을 넣어도 보내는 본문이 JSON으로 바뀌지는 않는다. 또한 Content-Encoding: gzip이라는 문구만 추가한다고 본문이 압축되지 않는다. 선언과 실제 전송 내용이 맞는지를 각각 확인해야 한다.

첫 단계: 실패한 요청의 전송 직전 상태를 남긴다

애플리케이션에서 만든 객체만 보지 말고 HTTP 라이브러리가 직렬화한 뒤의 요청을 확인한다. 폼 전송 옵션을 사용했거나 공통 미들웨어가 헤더를 덮었다면, 코드에 보이는 JSON과 수신 서버가 받은 형식이 달라질 수 있다. 이는 점검할 후보이며, 로그 없이 현재 장애의 원인이라고 결론내릴 수는 없다.

다음 양식은 복사해 쓰는 진단 기록이다. 인증값·쿠키·고객 원문은 넣지 않는다. 본문은 비밀정보를 제거한 시험 입력의 형식만 기록하고, 운영 데이터 전체를 공개 로그에 남기지 않는다.

관측 시각 / 시간대:
대상 환경 / 정확한 경로 / HTTP 메서드:
클라이언트·SDK 버전 / 서버 버전:
요청 Content-Type:
요청 Content-Encoding: (없으면 생략으로 기록)
요청 Accept:
직렬화 결과: JSON 객체 / JSON 문자열 / 폼 / 기타
실제 본문 압축 여부:
응답 상태 / 응답 Content-Type:
응답 Accept / Accept-Encoding: (제공된 경우만)
거절 구간을 확인한 근거:
변경한 항목 / 변경 뒤 관측:

두 번째 단계: 세 가지 가상 사례를 다르게 처리한다

사례 A. JSON처럼 보이지만 폼으로 전송했다. 요청 헤더가 application/x-www-form-urlencoded이고 본문이 jsonrpc=2.0&method=tools%2Flist라면, 헤더만 JSON으로 바꾸지 않는다. HTTP 라이브러리의 JSON 직렬화 경로를 사용해 본문 자체를 JSON 객체로 보낸다. 사용하는 라이브러리의 정확한 버전 문서에서 옵션을 확인한다.

사례 B. gzip 선언과 본문이 다르다. 평문 JSON에 gzip 헤더가 붙었거나, 압축한 바이트에 그 헤더가 빠졌는지 확인한다. 서버가 압축 요청을 받지 않는 경우도 구별한다. 시험 환경에서 압축을 끄려면 헤더와 실제 본문 변환을 함께 맞춰야 한다. 헤더만 지워 압축 바이트를 평문처럼 보내는 것은 수정이 아니다.

사례 C. 헤더는 맞지만 다른 주소가 응답했다. MCP 엔드포인트가 아니라 업로드 API나 로그인 경로로 갔는지, 중간 게이트웨이가 거절했는지 확인한다. HTML 오류 화면만으로 어느 서버가 만든 응답인지 확정하지 않는다. 접근 가능한 게이트웨이·애플리케이션 기록을 시각과 요청 식별자로 대조한다. 제공되지 않은 식별자는 만들어 넣지 않는다.

MCP 요청 헤더와 응답 형식을 섞지 않는다

MCP 2026-07-28 Streamable HTTP는 각 JSON-RPC 요청을 별도 POST로 보내고, 클라이언트가 JSON과 SSE 응답을 모두 수용하도록 정한다. 아래는 형식 비교용 헤더 일부다. 완전한 요청 예제나 실행 명령이 아니다. 버전·메서드 등 필수 메타데이터와 본문의 일치는 별도로 갖춰야 한다. MCP Streamable HTTP 규격

Content-Type: application/json
Accept: application/json, text/event-stream

여기서 응답을 SSE로 받을 수 있다는 이유로 요청 본문의 Content-Type을 text/event-stream으로 바꾸면 역할이 뒤바뀐다. 반대로 응답이 SSE인데 JSON 객체 하나로 파싱하는 문제는, 요청 형식을 거절하는 415와 다른 진단 경로다.

서버가 415 응답에 Accept-Encoding을 제공했다면 요청 압축 지원을 확인할 단서가 된다. 응답의 Accept는 수용 가능한 요청 미디어 유형을 알리는 데 쓰일 수 있다. 다만 헤더가 없다는 이유만으로 압축이 원인이 아니라고 확정하지 않는다. 응답 본문과 해당 서버의 문서를 함께 읽는다.

수정은 한 항목씩, 완료 판정은 두 단계로

안전한 시험 환경에서 동일한 비민감 입력으로 한 항목씩 비교한다. 주소와 메서드, 인증 범위까지 한꺼번에 바꾸면 어떤 변화가 영향을 주었는지 알기 어렵다. 운영 쓰기 요청을 반복해 테스트하지 말고, 결과가 불확실한 기존 쓰기는 상태 조회로 확인한 뒤 재시도 여부를 정한다.

첫 단계는 415가 사라지고 서버가 요청을 해석했는지 확인하는 것이다. 그다음에는 JSON-RPC 응답과 도구 결과를 읽는다. HTTP 200이 나와도 프로토콜 오류나 isError: true가 남을 수 있다. 반대로 400으로 바뀌었다면 새 응답 본문을 읽어 원인을 분리한다. 오류 숫자가 달라졌다는 것만으로 연결이 고쳐졌다고 쓰지 않는다.

원인 기록은 “헤더 수정 후 성공”보다 좁고 정확해야 한다. 예를 들어 “시험 입력에서 폼 직렬화를 JSON 직렬화로 바꾼 뒤 415가 재현되지 않았고, 목록 응답을 확인했다”라고 남긴다. 이런 결론은 해당 관측이 실제로 확보됐을 때만 쓸 수 있다. 다른 클라이언트나 프록시까지 해결됐다는 뜻은 아니다.

함께 확인할 글

큰 요청만 413으로 실패할 때: 본문의 형식보다 크기 제한을 먼저 의심해야 하는 경우다.

MCP HTTP 400과 HeaderMismatch 진단: 미디어 유형이 아니라 메타데이터와 본문이 어긋난 경우를 다룬다.

SSE data와 이벤트 경계 진단: 요청이 수락된 뒤 응답을 읽는 단계에서 실패한다면 이 순서로 확인한다.

적용 범위와 출처

확인일: 2026년 10월 16일 KST. 기준 문서: MCP 2026-07-28 Streamable HTTP, RFC 9110 §15.5.16 및 같은 문서의 §8.3·§8.4·§12.5.3. 특정 SDK·호스트의 415 처리 방식, 서버 압축 지원, 프록시 동작은 이 글에서 실측하지 않았다. 진단 순서는 문서에 근거한 제안이며 가상 사례는 실제 실행 결과가 아니다.


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

RUDA DIRECTOR에서 더 알아보기

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

계속 읽기