Skip to content

Commit 97ba229

Browse files
test: expect rejection of client-supplied resolved parameters
1 parent eb768d3 commit 97ba229

2 files changed

Lines changed: 11 additions & 8 deletions

File tree

docs/handlers/dependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Here is the input schema `tools/list` reports for `reserve_book`:
3535
}
3636
```
3737

38-
One property. Like the `Context` in **[The Context](context.md)**, a resolved parameter is a contract between you and the SDK: `stock` is not in the schema, the model is never told about it, and a client that sends a `stock` value anyway is ignored. The resolver's value is the only one your tool can receive.
38+
One property. Like the `Context` in **[The Context](context.md)**, a resolved parameter is a contract between you and the SDK: `stock` is not in the schema, the model is never told about it, and a client that sends a `stock` value anyway is rejected. The resolver's value is the only one your tool can receive.
3939

4040
That last part is the point. A parameter the model cannot supply is a parameter the model cannot get wrong.
4141

tests/server/mcpserver/test_resolve.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,23 +2719,25 @@ async def quote(title: str, total: Annotated[int, Resolve(with_tax)]) -> str:
27192719

27202720

27212721
@pytest.mark.anyio
2722-
async def test_a_client_supplied_value_for_a_resolved_parameter_is_discarded():
2723-
"""A value the client sends under a resolved parameter's name never reaches the
2724-
tool; the resolver's value wins. SDK-defined: resolved parameters are server-side only."""
2722+
async def test_a_client_supplied_value_for_a_resolved_parameter_is_rejected():
2723+
"""A value the client sends under a resolved parameter's name fails validation.
2724+
SDK-defined: resolved parameters are absent from the schema, so extras are forbidden."""
27252725
mcp = MCPServer(name="Walk", request_state_security=RequestStateSecurity.ephemeral())
27262726

27272727
def price_of(title: str) -> int:
2728-
return 42
2728+
return 42 # pragma: no cover - never reached; call fails at arg validation
27292729

27302730
@mcp.tool()
27312731
async def quote(title: str, price: Annotated[int, Resolve(price_of)]) -> str:
2732-
return f"{title}: {price}"
2732+
return f"{title}: {price}" # pragma: no cover - never reached; call fails at arg validation
27332733

27342734
async with Client(mcp) as client:
27352735
result = await client.call_tool("quote", {"title": "Dune", "price": 999})
27362736

2737-
assert not result.is_error
2738-
assert result.content == [TextContent(type="text", text="Dune: 42")]
2737+
assert result.is_error
2738+
assert isinstance(result.content[0], TextContent)
2739+
# pydantic's "Extra inputs are not permitted" wording changes across versions.
2740+
assert result.content[0].text.startswith("Error executing tool quote:")
27392741

27402742

27412743
@pytest.mark.anyio
@@ -2757,6 +2759,7 @@ async def quote(title: str, price: Annotated[int, Resolve(price_of)]) -> str:
27572759
assert advertised.input_schema == snapshot(
27582760
{
27592761
"type": "object",
2762+
"additionalProperties": False,
27602763
"properties": {"title": {"title": "Title", "type": "string"}},
27612764
"required": ["title"],
27622765
"title": "quoteArguments",

0 commit comments

Comments
 (0)