Streamable HTTP 전송 가이드

Streamable HTTP 전송 가이드

Streamable HTTP 전송에서 서버는 여러 클라이언트 연결을 처리할 수 있는 독립 프로세스로 동작해요. 한눈에 보면 이렇습니다:

  • 서버는 POST를 받는 단일 HTTP 엔드포인트(MCP 엔드포인트)를 노출합니다.
  • 클라이언트는 모든 JSON-RPC 요청이나 알림을 각각 하나의 HTTP POST로 보냅니다.
  • 서버는 각 요청에 단일 JSON 객체 또는 그 요청에 한정된 Server-Sent Events(SSE) 스트림으로 응답합니다. 이 스트림은 요청 관련 알림을 실어 보낸 뒤 최종 응답으로 끝나요.
  • 서버→클라이언트 상호작용(sampling, elicitation, roots)은 Multi Round-Trip Requests (MRTR)에 따라 입력 요청으로 결과에 포함됩니다(SEP-2322).
  • 오래 지속되는 변경 알림(목록 변경, 리소스 갱신 등)은 subscriptions/listen 요청의 응답 스트림으로 전달됩니다.

이 상호작용들의 시퀀스 다이어그램은 Message Flow에서 확인할 수 있어요.

서버는 POST를 지원하는 단일 HTTP 엔드포인트 경로(이하 MCP 엔드포인트)를 반드시(MUST) 제공해야 합니다. 예를 들어 https://example.com/mcp 같은 URL이 될 수 있어요.

참고 (도입 배경): Streamable HTTP는 프로토콜 버전 2025-03-26에서 프로토콜 버전 2024-11-05의 HTTP+SSE 전송을 대체하기 위해 도입되었어요.

참고 (2026-07-28 개정): 개정 2026-07-28은 Streamable HTTP의 동작을 바꿨습니다. 클라이언트는 하위 호환을 올바르게 처리해야 해요. 변경 사항은 다음과 같아요:

  • GET 스트림 엔드포인트 제거
  • 프로토콜 수준 세션 제거

자세한 내용은 changelog와 아래 Backward Compatibility를 참고하세요.

출처: MCP 공식 문서 — Streamable HTTP Transport

보안 & 엔드포인트 (Security & Endpoint)

Streamable HTTP 전송을 구현할 때:

  1. 서버는 DNS 리바인딩(DNS rebinding) 공격을 막기 위해 모든 수신 연결의 Origin 헤더를 검증해야 합니다(MUST).
    • Origin 헤더가 있고 유효하지 않으면 서버는 HTTP 403 Forbidden으로 응답해야 합니다(MUST). HTTP 응답 본문은 id가 없는 JSON-RPC 오류 응답으로 구성할 수 있습니다(MAY).
  2. 로컬에서 실행할 때 서버는 모든 네트워크 인터페이스(0.0.0.0)가 아니라 localhost(127.0.0.1)에만 바인딩하는 것이 좋습니다(SHOULD).
  3. 서버는 모든 연결에 적절한 인증을 구현해야 합니다(SHOULD).

이 보호가 없으면 공격자가 DNS 리바인딩을 이용해 원격 웹사이트에서 로컬 MCP 서버와 상호작용할 수 있어요.

메시지 보내기 (Sending Messages)

클라이언트가 보내는 모든 JSON-RPC 메시지는 MCP 엔드포인트로 가는 새 HTTP POST 요청이어야 합니다(MUST).

  1. 클라이언트는 JSON-RPC 메시지를 보낼 때 HTTP POST를 사용해야 합니다(MUST).
  2. 클라이언트는 Accept 헤더에 지원 콘텐츠 유형으로 application/jsontext/event-stream모두 나열해야 합니다(MUST).
  3. 클라이언트는 각 POST 요청에 요청 메타데이터 헤더포함해야 합니다(MUST).
  4. HTTP POST의 본문은 단일 JSON-RPC 요청 또는 알림이어야 합니다(MUST). 클라이언트는 JSON-RPC 응답을 보내면 안 됩니다(MUST NOT).
  5. 본문이 JSON-RPC 알림이면:
    • 서버가 수락하면 서버는 본문 없이 HTTP 상태 코드 202 Accepted반환해야 합니다(MUST).
    • 서버가 수락할 수 없으면 HTTP 오류 상태 코드(예: 400 Bad Request)를 반환해야 합니다(MUST). HTTP 응답 본문은 id가 없는 JSON-RPC 오류 응답으로 구성할 수 있습니다(MAY).
  6. 본문이 JSON-RPC 요청이면 서버는 Content-Type: application/json(단일 JSON 객체) 또는 Content-Type: text/event-stream(SSE 응답 스트림) 중 하나를 반환해야 합니다(MUST). 클라이언트는 둘 다 지원해야 합니다(MUST).

참고: 이번 코어 프로토콜 개정은 Streamable HTTP 위에서 클라이언트→서버 알림을 정의하지 않아요. 코어 프로토콜의 유일한 클라이언트 발신 알림인 notifications/cancelledstdio 전송에서만 사용됩니다. Streamable HTTP에서는 SSE 응답 스트림을 닫는 것 자체가 취소 신호라 notifications/cancelled 메시지는 기대하지 않아요(see Cancellation). 위 알림 규칙은 알림 POST의 전송 메커니즘을 설명하며, 알림 POST의 헤더 요건은 이번 개정이 정의하지 않습니다.

메시지 받기 (Receiving Messages)

서버가 SSE 응답 스트림(Content-Type: text/event-stream)을 반환할 때:

  • 서버는 최종 응답 전에 JSON-RPC 알림 — 예를 들어 notifications/progressnotifications/message — 을 보낼 수 있습니다(MAY). 이 알림들은 발원된 클라이언트 요청과 관련되어야 합니다(MUST).
  • 서버는 이 스트림에서 독립적인 JSON-RPC 요청을 보내면 안 됩니다(MUST NOT). 서버→클라이언트 상호작용(sampling, elicitation, list-roots)은 MRTR(SEP-2322)에 따라 InputRequiredResult 안에 입력 요청으로 포함되며, 이 스트림이나 다른 스트림에서 별도 요청으로 전달되지 않아요. 이는 프로토콜 버전 2025-03-26부터 2025-11-25까지의 Streamable HTTP가 SSE 스트림에서 그런 요청을 보낼 수 있었던 것과 대비되는 변화입니다.
  • 최종 JSON-RPC 응답은 스트림을 종료해야 합니다(SHOULD).

오래 지속되는 알림 스트림은 subscriptions/listen 요청을 보내 얻습니다. 서버의 응답 그 자체가 열려 있는 SSE 스트림이고, 클라이언트가 옵트인한 변경 알림(예: notifications/tools/list_changed, notifications/resources/updated)을 전달합니다. notifications/progress, notifications/message 같은 요청 범위 알림은 listen 스트림으로 전달되지 않아요 — 관련 요청의 응답 스트림에서만 흐릅니다.

SSE 스트림을 시작할 때 서버는 HTTP 응답에 X-Accel-Buffering: no 헤더를 포함하는 것이 좋습니다(SHOULD). 이는 nginx 같은 리버스 프록시가 응답 버퍼링을 끄도록 지시해, SSE 이벤트가 버퍼에 머물지 않고 클라이언트에 즉시 전달되게 합니다. 이 헤더가 없으면 프록시가 메시지를 모아뒀다 보낼 수 있어 불필요한 지연이 생기고 SSE 통신의 실시간성이 깨질 수 있어요.

참고 (keep-alive): 오래 지속되는 스트림 — 특히 subscriptions/listen 응답 스트림 — 에서는 서버가 주기적으로 SSE 주석 줄(콜론으로 시작하는 줄, 예: :\r\n)을 keep-alive로 내보내는 것을 권장해요. 알림이 흐르지 않는 조용한 구간에 중간 장비나 클라이언트의 유휴 타임아웃으로 연결이 닫히는 걸 막아줍니다. SSE 명세에 따라 콜론으로 시작하는 어떤 줄도 이벤트 데이터를 담지 않는 주석이고, 클라이언트는 그런 줄을 무시하며 잘못된 입력으로 취급하면 안 됩니다.

Last-Event-ID를 통한 이력 가능한(resumable) SSE 스트림은 지원되지 않아요.

메시지 흐름 (Message Flow)

아래 다이어그램은 단일 MCP 엔드포인트에서의 메시지 흐름을 보여줍니다.

요청과 응답. 각 요청은 각자의 POST이며, 서버는 요청별로 단일 JSON 객체 또는 SSE 스트림 중 무엇으로 응답할지 정합니다:

sequenceDiagram
    participant Client
    participant Server

    note over Client,Server: Simple response
    Client->>Server: POST tools/call (JSON-RPC request)
    Server-->>Client: 200 OK, application/json<br/>JSON-RPC response

    note over Client,Server: Streaming response
    Client->>Server: POST tools/call (JSON-RPC request)
    note over Server: Opens SSE stream<br/>scoped to this request
    Server-->>Client: SSE: notifications/progress
    Server-->>Client: SSE: notifications/progress
    Server-->>Client: SSE: JSON-RPC response
    note over Client,Server: Stream closes

    note over Client,Server: Notification
    Client->>Server: POST (JSON-RPC notification)
    Server-->>Client: 202 Accepted

서버→클라이언트 상호작용 (MRTR). 서버가 클라이언트 입력(sampling, elicitation, roots)이 필요할 때 자체 JSON-RPC 요청을 보내지 않아요. inputRequests를 담은 InputRequiredResult를 돌려주고, 클라이언트는 일치하는 inputResponses를 붙여 원래 요청을 재시도합니다(see Multi Round-Trip Requests):

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: POST tools/call (id: 1)
    note over Server: Needs user input or<br/>an LLM completion
    Server-->>Client: InputRequiredResult<br/>(inputRequests: elicitation/create)
    note over Client: Gathers the requested input
    Client->>Server: POST tools/call (id: 2)<br/>(original params + inputResponses)
    Server-->>Client: Final result

변경 알림. 서버 발신 변경 알림을 원하는 클라이언트는 subscriptions/listen으로 오래 지속되는 스트림을 엽니다. 응답 스트림은 열린 채로 유지되며 클라이언트가 옵트인한 알림 유형만 실어 보냅니다:

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: POST subscriptions/listen<br/>(notification filter)
    Server-->>Client: SSE: notifications/subscriptions/acknowledged
    note over Client,Server: Stream stays open
    Server-->>Client: SSE: notifications/tools/list_changed
    Server-->>Client: SSE: notifications/resources/updated
    note over Client,Server: Until the client or server closes the stream

취소 (Cancellation)

서버는 SSE 응답 스트림을 닫는 것을 그 요청의 취소로 반드시(MUST) 처리해야 합니다. 각 요청이 자기만의 응답 스트림을 갖기 때문에 전송 수준 연결 해제는 모호하지 않아요. 서버는 취소된 요청에 대한 작업을 가능한 빨리 멈추고(SHOULD) 더 이상 메시지를 보내지 않아야 합니다(MUST NOT). 전체 규칙은 Cancellation에서 확인하세요.

요청 메타데이터 (Request Metadata)

Streamable HTTP 전송은 선택된 JSON-RPC 본문 필드를 HTTP 헤더로 미러링해서, 중간 장비(로드 밸런서, 게이트웨이, 관측 도구)가 본문을 파싱하지 않고도 요청을 라우팅하고 검사할 수 있게 해줘요.

프로토콜 버전 헤더 (Protocol Version Header)

MCP 엔드포인트로 가는 모든 POST 요청은 MCP-Protocol-Version 헤더를 포함해야 합니다(MUST).

예: MCP-Protocol-Version: 2026-07-28

헤더 값은 요청 본문의 _meta에 담긴 io.modelcontextprotocol/protocolVersion 필드와 일치해야 합니다(MUST). 값이 다르면 서버는 400 Bad RequestHeaderMismatch JSON-RPC 오류로 요청을 거부해야 합니다(MUST)(see Server Validation).

서버가 요청된 프로토콜 버전을 구현하지 않으면(서버가 모르는 버전이거나, 알지만 지원하지 않기로 한 버전) 400 Bad Request와 지원 버전을 나열하는 UnsupportedProtocolVersionError응답해야 합니다(MUST). 협상 흐름은 Versioning: Protocol Version Negotiation을 참고하세요.

서버가 요청된 RPC 메서드를 구현하지 않으면 404 Not Found와 코드 -32601(Method not found)의 JSON-RPC 오류로 응답해야 합니다(MUST). JSON-RPC 오류 본문은 이 경우를 현대 MCP 엔드포인트를 호스팅하지 않는 레거시 HTTP+SSE 서버가 돌려주는 404와 구분해 줍니다(see Backward Compatibility).

2025-06-18 이전(즉 MCP-Protocol-Version 헤더를 정의하지 않은) 프로토콜 버전을 구현하는 클라이언트를 지원하는 서버는, 헤더를 생략한 요청을 프로토콜 버전 2025-03-26으로 취급할 수 있습니다(MAY). 그런 클라이언트를 지원하지 않는 서버는 Server Validation에 따라 헤더 없는 요청을 반드시(MUST) 거부해야 합니다.

표준 요청 헤더 (Standard Request Headers)

헤더 이름 원본 필드 필요 조건
Mcp-Method method 모든 요청
Mcp-Name params.name 또는 params.uri tools/call, resources/read, prompts/get 요청

이 헤더들은 준수(compliance)를 위해 필수(REQUIRED) 예요.

Mcp-Name 원본 값이 일반 ASCII 헤더 값으로 안전하게 표현될 수 없으면, 클라이언트는 Value Encoding에서 설명하는 Base64 센티넬 형식으로 인코딩해야 합니다(MUST).

tools/call 요청:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "Seattle, WA"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

resources/read 요청:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: resources/read
Mcp-Name: file:///projects/myapp/config.json

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/read",
  "params": {
    "uri": "file:///projects/myapp/config.json",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

도구 파라미터에서 오는 커스텀 헤더 (Custom Headers from Tool Parameters)

MCP 서버는 도구의 inputSchema 내 파라미터 스키마에 x-mcp-header 확장 속성을 써서, 특정 도구 파라미터를 HTTP 헤더로 미러링하도록 지정할 수 있습니다(MAY). 도구 파라미터 주석 방법은 Tool Definitions에서 확인하세요.

서버에게 x-mcp-header 사용은 선택이지만, 클라이언트는 이 기능을 지원해야 합니다(MUST). 서버의 도구 정의에 x-mcp-header 주석이 있으면 준수 클라이언트는 지정된 파라미터 값을 HTTP 헤더로 미러링해야 합니다(MUST).

스키마 확장 (Schema Extension)

x-mcp-header 속성은 헤더 이름 Mcp-Param-{name}을 만들 때 쓰는 이름 부분을 지정합니다.

x-mcp-header 값의 제약:

  • 비어 있으면 안 됨(MUST NOT)
  • HTTP 필드 이름 토큰 문법(1*tchar, RFC 9110 Section 5.1)과 일치해야 함(MUST)
  • 캐리지 리턴(CR, \r)이나 라인 피드(LF, \n)를 포함한 제어 문자를 포함해선 안 됨(MUST NOT)
  • inputSchema의 모든 x-mcp-header 값 사이에서 대소문자 무관하게 유일해야 함(MUST)
  • 정수(integer), 문자열(string), 불리언(boolean) 같은 기본 타입 파라미터에만 적용해야 함(MUST). 타입 number 파라미터는 허용되지 않아요. 정수 값은 JavaScript의 안전 범위(−253+1 ~ 253−1) 안이어야 함(MUST)
  • 스키마 루트에서 정적으로 도달 가능(statically reachable) 한 속성에만 적용해야 함(MUST): 오직 properties 키로만 이뤄진 체인으로 도달 가능해야 해요. 그 체인은 items(또는 다른 배열 키워드), 구성 키워드(oneOf, anyOf, allOf, not), 조건 키워드(if/then/else), $ref통과해선 안 됩니다(MUST NOT). 중첩 객체 속성은 체인의 모든 단계가 properties 키인 한 허용됩니다. 다른 곳에 있는 x-mcp-header 주석은 그 주석 — 따라서 도구 정의 — 을 무효로 만들어요.

헤더 추출은 주석이 달린 속성의 정확한 속성 경로(properties 키 체인)에서 인스턴스 값을 읽는 것으로 정의됩니다. 호출 인자에서 그 경로에 값이 없으면 헤더는 생략됩니다.

Streamable HTTP 전송을 쓰는 클라이언트는 어떤 x-mcp-header 값이든 이 제약을 위반하는 도구 정의를 거부해야 합니다(MUST). 거부란 클라이언트가 tools/list 결과에서 그 잘못된 도구를 제외해야 한다는 뜻이에요(MUST). 도구 정의를 거부할 때 클라이언트는 도구 이름과 거부 이유를 포함한 경고를 로깅하는 것이 좋습니다(SHOULD). 이렇게 하면 단 하나의 잘못된 도구 정의가 다른 유효한 도구 사용을 막지 않아요. 다른 전송(예: stdio)을 쓰는 클라이언트는 x-mcp-header 주석을 완전히 무시할 수 있습니다(MAY).

예시 도구 정의:

{
  "name": "execute_sql",
  "description": "Execute SQL on Google Cloud Spanner",
  "inputSchema": {
    "type": "object",
    "properties": {
      "region": {
        "type": "string",
        "description": "The region to execute the query in",
        "x-mcp-header": "Region"
      },
      "query": {
        "type": "string",
        "description": "The SQL query to execute"
      }
    },
    "required": ["region", "query"]
  }
}

결과 HTTP 요청:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    },
    "name": "execute_sql",
    "arguments": {
      "region": "us-west1",
      "query": "SELECT * FROM users"
    }
  }
}

값 인코딩 (Value Encoding)

클라이언트는 안전한 전송과 주입 공격 방지를 위해 HTTP 헤더에 넣기 전에 파라미터 값을 인코딩해야 합니다(MUST).

타입 변환: 파라미터 값을 문자열 표현으로 변환하세요:

  • string: 값을 그대로 사용
  • integer: 십진수 문자열 표현으로 변환(예: 42, -7)
  • boolean: 소문자 "true" 또는 "false"로 변환

RFC 9110에 따라 HTTP 헤더 필드 값은 보이는 ASCII 문자(0x21-0x7E), 공백(0x20), 가로 탭(0x09)으로 구성되어야 합니다. 값이 일반 ASCII 헤더 값으로 안전하게 표현될 수 없으면(비-ASCII 문자, 제어 문자, 앞뒤 공백 포함) 클라이언트는 UTF-8 표현의 Base64 인코딩을 다음 형식으로 사용해야 합니다(MUST):

Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=

동일한 인코딩 규칙이 Mcp-Name 헤더 값에도 적용됩니다. 도구·프롬프트 이름은 헤더 안전 문자로 제약하는 것이 SHOULD 수준일 뿐이라, 안전 집합 밖의 이름(또는 리소스 URI)은 다음과 같이 실어 보냅니다:

Mcp-Name: =?base64?{Base64EncodedValue}?=

접두사 =?base64?와 접미사 ?=는 값이 Base64 인코딩됨을 뜻해요. 이 마커들은 대소문자를 구분하며 정확히 보이는 그대로(소문자) 나타나야 합니다(MUST). 이 값을 검사해야 하는 서버·중간 장비는 그에 맞게 디코딩해야 합니다(MUST). 특히 서버는 Server Validation에서 본문 값과 비교하기 전에 인코딩된 Mcp-Name이나 Mcp-Param-{Name} 값을 디코딩해야 합니다(MUST).

모호성을 피하려고, 클라이언트는 센티넬 패턴(즉 =?base64?로 시작하고 ?=로 끝나는)과 일치하는 일반 ASCII 값도 Base64 인코딩해야 합니다(MUST).

인코딩 예시:

원본 값 이유 인코딩된 헤더 값
"us-west1" 일반 ASCII Mcp-Param-Region: us-west1
"Hello, 世界" 비-ASCII 포함 Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=
" padded " 앞뒤 공백 Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=
"line1\nline2" 새 줄 포함 Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=
"=?base64?literal?=" 센티넬 패턴과 일치 Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=

클라이언트 동작 (Client Behavior)

HTTP 전송으로 tools/call 요청을 만들 때 클라이언트는 반드시(MUST):

  1. 요청 본문에서 표준 헤더 값을 추출한다(예: method, params.name, params.uri).
  2. Mcp-Method 헤더와, 해당되면 Mcp-Name 헤더를 요청에 붙인다.
  3. 도구의 inputSchema에서 x-mcp-header가 표시된 속성을 찾고, 주석이 달린 각 속성의 정확한 속성 경로에서 값을 추출한다. 값이 없으면 헤더를 생략한다(see Schema Extension).
  4. Value Encoding 규칙에 따라 값을 인코딩한다.
  5. 요청에 Mcp-Param-{Name}: {Value} 헤더를 붙인다.

필요한 Mcp-Param-* 헤더가 빠졌거나 본문과 일치하지 않아 서버가 HeaderMismatch 오류로 요청을 거부하면, 클라이언트는 tools/list를 호출해 도구의 inputSchema 변경이 있는지 확인한 뒤 적절한 헤더로 원래 요청을 재시도하는 것이 좋습니다(SHOULD).

커스텀 헤더에 대한 서버 동작 (Server Behavior for Custom Headers)

Mcp-Param-{Name} 헤더를 인식하지 못하는 중간 서버는 HTTP Semantics RFC에 따라 그것을 전달하고 그 외에는 무시해야 합니다(MUST).

서버는 잘못된 문자를 포함하는 인지된 Mcp-Param-{Name} 헤더가 있는 요청을 거부해야 합니다(MUST)(see Value Encoding).

메시지 본문을 처리하는 어떤 서버든, 디코딩 후(Base64 인코딩된 경우) 인코딩된 헤더 값이 요청 본문의 해당 값과 일치하는지 검증해야 합니다(MUST). 검증이 실패하면 서버는 HTTP 상태 400 Bad Request와 JSON-RPC 오류 코드 -32020(HeaderMismatch)으로 요청을 거부해야 합니다(MUST).

시나리오 클라이언트 동작 서버 동작
파라미터 값 제공 클라이언트는 헤더를 포함해야 함 서버는 헤더가 본문과 일치함을 검증해야 함
파라미터 값이 null 클라이언트는 헤더를 생략해야 함 서버는 헤더를 기대하면 안 됨
파라미터가 인자에 없음 클라이언트는 헤더를 생략해야 함 서버는 헤더를 기대하면 안 됨
클라이언트가 헤더를 생략했는데 값이 본문에 있음 비준수 클라이언트 서버는 요청을 거부해야 함

대소문자 구분 (Case Sensitivity)

헤더 이름(RFC 9110의 "field names")은 대소문자를 구분하지 않아요. 클라이언트와 서버는 헤더 이름 비교에 대소문자 무관 비교를 사용해야 합니다(MUST). 헤더 (메서드 이름 같은)은 대소문자를 구분합니다.

서버 검증 (Server Validation)

요청 본문을 처리하는 서버는 헤더에 지정된 값이 요청 본문의 해당 값과 일치하지 않는 요청을 거부해야 합니다(MUST). 네트워크의 서로 다른 구성 요소가 서로 다른 진실 원천에 의존할 때(예: 로드 밸런서는 헤더 값으로 라우팅하는데 MCP 서버는 본문 값으로 실행하는 경우) 발생할 수 있는 보안 취약점을 막아줘요.

참고: 정수 파라미터 값을 검증할 때 서버는 헤더 값과 본문 값을 문자열이 아니라 수치로 비교하는 것이 좋습니다(SHOULD)(예: 42.042는 같게 취급).

헤더 검증 실패로 요청을 거부할 때 서버는 HTTP 상태 400 Bad Request반환해야 하고(MUST), 다음 오류 코드를 쓰는 JSON-RPC 오류 응답을 포함해야 합니다(MUST):

코드 이름 설명
-32020 HeaderMismatch HTTP 헤더가 요청 본문의 해당 값과 일치하지 않거나, 필수 헤더가 없거나 잘못됨

이 오류 코드는 MCP 명세가 프로토콜 정의 오류로 예약한 하위 범위에서 할당됩니다. Error Codes를 참고하세요.

예시 오류 응답:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32020,
    "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
  }
}

검증 실패 조건에는 다음이 포함됩니다:

  • 필수 표준 헤더(MCP-Protocol-Version, Mcp-Method, Mcp-Name)가 없음.
  • 헤더 값이 요청 본문의 해당 값과 일치하지 않음. Base64 센티넬 인코딩을 허용하는 헤더(Mcp-NameMcp-Param-{Name})에 대해 서버는 본문 값과 비교하기 전에 인코딩된 값을 디코딩해야 합니다(MUST)(see Value Encoding).
  • 헤더 값에 잘못된 문자가 포함됨.

참고: 중간 장비는 검증 실패에 적절한 HTTP 오류 상태(예: 400 Bad Request)를 반환해야 하지만, JSON-RPC 오류 응답을 돌려줄 필요는 없어요.

참고: 미러링된 헤더를 기준으로 정책을 적용하는 중간 장비(예: 테넌트별 라우팅·속도 제한)는 MCP-Protocol-Version 헤더가 헤더-본문 검증을 요구하는 버전을 가리키는지 확인해야 합니다(SHOULD). 버전이 더 오래됐거나 헤더가 없으면, 중간 장비는 검증되지 않은 헤더 값을 신뢰하기보다 그 요청을 거부해야 합니다(SHOULD).

하위 호환성 (Backward Compatibility)

현대(요청별 메타데이터) MCP 버전과 initialize 핸드셰이크가 필요한 레거시 버전을 모두 지원하는 클라이언트는 현대 요청을 먼저 시도해 서버가 어느 시대를 구현하는지 감지할 수 있습니다(MAY). 400 Bad Request가 오면 클라이언트는 폴백하기 전에 응답 본문을 검사해야 합니다(SHOULD). 현대 서버도 UnsupportedProtocolVersionError, MissingRequiredClientCapabilityError, 헤더 검증 실패에 400을 쓰기 때문이에요.

  • 본문에 인지된 현대 JSON-RPC 오류가 있으면: 서버가 현대 MCP 버전을 말하는 것 — 폴백하지 말고 광고된 supported 버전으로 재시도하거나 요청을 고치세요.
  • 본문이 비어 있거나 인지된 현대 JSON-RPC 오류가 아니면: initialize로 폴백하고 이후 요청은 레거시 버전으로 계속하세요.

시대 모델과 구현자용 호환성 매트릭스는 Versioning: Backward Compatibility를 참고하세요.

이전 Streamable HTTP 개정판 (Earlier Streamable HTTP Revisions)

프로토콜 버전 2025-03-26부터 2025-11-25까지도 Streamable HTTP 전송을 사용했지만 그 형태가 달랐어요. 서버가 Mcp-Session-Id 헤더로 세션을 부여하고(HTTP DELETE로 종료), 클라이언트가 HTTP GET으로 서버 발신 메시지를 받기 위한 독립 SSE 스트림을 열 수 있었으며, 서버가 SSE 스트림에서 JSON-RPC 요청을 보낼 수 있었고, 스트림이 Last-Event-ID로 이력 가능(resumable)했죠. 이 메커니즘들은 이번 개정에 포함되지 않습니다.

이번 개정만 지원하는 서버가 옛 클라이언트로부터 그런 트래픽을 받으면 다음과 같이 응답해야 합니다(SHOULD):

  • MCP 엔드포인트로 가는 HTTP GET 또는 DELETE: 405 Method Not Allowed로 응답.
  • 요청의 Mcp-Session-Id 헤더: 무시하고, 세션 ID를 만들거나 되돌려주지 않음.
  • Last-Event-ID 헤더: 무시. 스트림은 이력 불가.

그 프로토콜 버전을 말하는 상대방과 상호운용해야 하는 서버·클라이언트는 위의 버전 협상 폴백과 함께 해당 개정에 설명된 동작(예: 2025-11-25: Streamable HTTP)을 구현합니다.

HTTP+SSE 전송 (2024-11-05)

경고 — 폐기(Deprecated): 프로토콜 버전 2024-11-05의 HTTP+SSE 전송은 프로토콜 버전 2025-03-26부터 폐기되었고, feature lifecycle policy(SEP-2596)에 따라 Deprecated로 분류됩니다. 새 구현은 채택하지 말아야 하며(SHOULD NOT), 기존 구현은 Streamable HTTP마이그레이션해야 합니다(SHOULD). 향후 개정에서 제거될 수 있으며, deprecated features registry를 참고하세요.

클라이언트와 서버는 폐기된(프로토콜 버전 2024-11-05) HTTP+SSE 전송과 하위 호환을 다음과 같이 유지할 수 있습니다.

옛 클라이언트를 지원하려는 서버는:

  • 옛 전송의 SSE 엔드포인트와 POST 엔드포인트를 Streamable HTTP 전송용 "MCP 엔드포인트"와 함께 계속 호스팅해야 합니다.
    • 옛 POST 엔드포인트와 새 MCP 엔드포인트를 합치는 것도 가능하지만 불필요한 복잡성이 생길 수 있어요.

옛 서버를 지원하려는 클라이언트는:

  1. 옛 전송을 쓰는 서버나 새 전송을 쓰는 서버를 가리킬 수 있는 MCP 서버 URL을 사용자로부터 받는다.
  2. 위에서 정의한 Accept 헤더로 서버 URL에 POST 요청을 시도한다:
    • 성공하면 새 Streamable HTTP 전송을 지원하는 서버로 간주할 수 있다.
    • HTTP 상태 코드 400 Bad Request, 404 Not Found, 405 Method Not Allowed로 실패하고 응답 본문이 인지된 현대 JSON-RPC 오류가 아니면(현대 서버는 지원 안 하는 버전, 모르는 메서드, 헤더 검증 실패에 그런 오류를 돌려줌):
      • 서버 URL에 GET 요청을 보내 SSE 스트림이 열리고 첫 번째 이벤트로 endpoint 이벤트가 올 것으로 기대한다.
      • endpoint 이벤트가 오면 옛 HTTP+SSE 전송을 실행하는 서버로 간주하고, 이후 모든 통신에 그 전송을 사용한다.

더 알아보기 (Learn more)