Skip to content

Commit 9ce1a60

Browse files
committed
fix: send SEP-2243 streamable HTTP headers
1 parent 616476f commit 9ce1a60

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

src/mcp/client/streamable_http.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343

4444
MCP_SESSION_ID = "mcp-session-id"
4545
MCP_PROTOCOL_VERSION = "mcp-protocol-version"
46+
MCP_METHOD = "mcp-method"
47+
MCP_NAME = "mcp-name"
4648
LAST_EVENT_ID = "last-event-id"
4749

4850
# Reconnection defaults
@@ -82,7 +84,7 @@ def __init__(self, url: str) -> None:
8284
self.session_id: str | None = None
8385
self.protocol_version: str | None = None
8486

85-
def _prepare_headers(self) -> dict[str, str]:
87+
def _prepare_headers(self, message: JSONRPCMessage | None = None) -> dict[str, str]:
8688
"""Build MCP-specific request headers.
8789
8890
These headers will be merged with the httpx.AsyncClient's default headers,
@@ -97,8 +99,28 @@ def _prepare_headers(self) -> dict[str, str]:
9799
headers[MCP_SESSION_ID] = self.session_id
98100
if self.protocol_version:
99101
headers[MCP_PROTOCOL_VERSION] = self.protocol_version
102+
if isinstance(message, JSONRPCRequest | JSONRPCNotification):
103+
headers[MCP_METHOD] = message.method
104+
if mcp_name := self._get_mcp_name(message):
105+
headers[MCP_NAME] = mcp_name
100106
return headers
101107

108+
def _get_mcp_name(self, message: JSONRPCRequest | JSONRPCNotification) -> str | None:
109+
params = message.params
110+
if not isinstance(params, dict):
111+
return None
112+
113+
if message.method in {"tools/call", "prompts/get"}:
114+
value = params.get("name")
115+
elif message.method in {"resources/read", "resources/subscribe", "resources/unsubscribe"}:
116+
value = params.get("uri")
117+
else:
118+
return None
119+
120+
if value is None:
121+
return None
122+
return str(value)
123+
102124
def _is_initialization_request(self, message: JSONRPCMessage) -> bool:
103125
"""Check if the message is an initialization request."""
104126
return isinstance(message, JSONRPCRequest) and message.method == "initialize"
@@ -253,8 +275,8 @@ async def _handle_resumption_request(self, ctx: RequestContext) -> None:
253275

254276
async def _handle_post_request(self, ctx: RequestContext) -> None:
255277
"""Handle a POST request with response processing."""
256-
headers = self._prepare_headers()
257278
message = ctx.session_message.message
279+
headers = self._prepare_headers(message)
258280
is_initialization = self._is_initialization_request(message)
259281

260282
async with ctx.client.stream(

tests/shared/test_streamable_http.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,31 @@ def test_server_validates_protocol_version_header(basic_server: None, basic_serv
17181718
assert response.status_code == 200
17191719

17201720

1721+
@pytest.mark.parametrize(
1722+
("method", "params", "expected_name"),
1723+
[
1724+
("tools/call", {"name": "echo_headers"}, "echo_headers"),
1725+
("prompts/get", {"name": "summarize"}, "summarize"),
1726+
("resources/read", {"uri": "file:///tmp/readme.md"}, "file:///tmp/readme.md"),
1727+
("resources/subscribe", {"uri": "file:///tmp/readme.md"}, "file:///tmp/readme.md"),
1728+
("resources/unsubscribe", {"uri": "file:///tmp/readme.md"}, "file:///tmp/readme.md"),
1729+
("tools/list", {}, None),
1730+
],
1731+
)
1732+
def test_streamable_http_client_adds_sep_2243_headers(method: str, params: dict[str, Any], expected_name: str | None):
1733+
"""POST requests include SEP-2243 method/name headers."""
1734+
transport = StreamableHTTPTransport("https://example.com/mcp")
1735+
message = JSONRPCRequest(jsonrpc="2.0", id=1, method=method, params=params)
1736+
1737+
headers = transport._prepare_headers(message)
1738+
1739+
assert headers["mcp-method"] == method
1740+
if expected_name is None:
1741+
assert "mcp-name" not in headers
1742+
else:
1743+
assert headers["mcp-name"] == expected_name
1744+
1745+
17211746
def test_server_backwards_compatibility_no_protocol_version(basic_server: None, basic_server_url: str):
17221747
"""Test server accepts requests without protocol version header."""
17231748
# First initialize a session to get a valid session ID

0 commit comments

Comments
 (0)