Skip to content

Commit 64fab09

Browse files
committed
Say on the Header parameters page that Client re-lists and resends
One bullet under "Mark an argument", with a test that calls the tutorial's tool without listing it first.
1 parent a57f2e4 commit 64fab09

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

‎docs/advanced/header-parameters.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The mark is one extra key in the argument's JSON Schema. On `MCPServer`, `Field`
1313
```
1414

1515
* Over Streamable HTTP on `2026-07-28`, a client that has listed the tool sends `Mcp-Param-Region` alongside the body, and the server rejects a call where the two disagree.
16+
* A client that hasn't listed the tool yet sends no header, and the call is rejected. This SDK's `Client` then lists the tools and resends the call once, so listing first only saves a round trip.
1617
* Every other connection ignores the annotation.
1718

1819
Your function doesn't change: `region` still arrives as an argument.

‎tests/docs_src/test_header_parameters.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@ async def test_a_call_whose_header_and_body_disagree_is_rejected() -> None:
6363
assert tampered.json()["error"]["code"] == HEADER_MISMATCH
6464

6565

66+
async def test_a_client_that_has_not_listed_the_tool_is_rejected_then_lists_and_resends_once() -> None:
67+
"""tutorial001: the first call has no header and is a 400; after one `tools/list` it is resent with the header."""
68+
app = tutorial001.mcp.streamable_http_app()
69+
exchanges: list[tuple[str, str | None, int]] = []
70+
71+
async def record(response: httpx2.Response) -> None:
72+
sent = response.request.headers
73+
exchanges.append((sent["mcp-method"], sent.get("mcp-param-region"), response.status_code))
74+
75+
async with (
76+
app.router.lifespan_context(app),
77+
httpx2.ASGITransport(app) as transport,
78+
httpx2.AsyncClient(transport=transport, event_hooks={"response": [record]}) as http,
79+
Client(streamable_http_client(URL, http_client=http)) as client,
80+
):
81+
result = await client.call_tool("check_stock", ARGUMENTS)
82+
assert result.structured_content == {"result": "Dune: 3 copies in eu."}
83+
assert exchanges == [
84+
("server/discover", None, 200),
85+
("tools/call", None, 400),
86+
("tools/list", None, 200),
87+
("tools/call", "eu", 200),
88+
]
89+
90+
6691
async def test_a_legacy_http_connection_ignores_the_annotation() -> None:
6792
"""tutorial001: before 2026-07-28 the same call succeeds and carries no `Mcp-Param-*` header."""
6893
async with check_stock_over_http(tutorial001.mcp.streamable_http_app(), mode="legacy") as (_, call):

0 commit comments

Comments
 (0)