Skip to content

Commit 8efa405

Browse files
authored
Bridge camelCase ToolAnnotations reads (#4597)
🤖 Generated with Codex
1 parent b0d3e65 commit 8efa405

4 files changed

Lines changed: 23 additions & 3 deletions

File tree

docs/development/v4-notes/change-register.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This is the complete register of user-facing changes from the MCP Python SDK v2
66

77
Each entry is tagged **Absorbed** (public surface unchanged), **Bridged** (shim keeps old code working, usually warning), **Breaking** (user code must change), or **Deprecated** (works, warns, slated for removal). See the [overview](/development/v4-notes/index) for what each disposition means.
88

9-
**Empirical validation (WS2 upgrade reality-check).** The register's compatibility claims are verified, not predicted. Running unchanged 3.x-era code against this branch, all 11 upgrade scenarios pass or warn — the only failures are the two predicted breaks, user `mcp.types` imports and positional `McpError(ErrorData(...))` construction. Cross-version wire interop between a 3.4.3 peer and this branch is bidirectionally clean across 9 operations (3.4.3 client ↔ v4 server and v4 client ↔ 3.4.3 server over HTTP). All 25 `_ALIASES` bridge entries warn correctly with actionable messages.
9+
**Empirical validation (WS2 upgrade reality-check).** The register's compatibility claims are verified, not predicted. Running unchanged 3.x-era code against this branch, all 11 upgrade scenarios pass or warn — the only failures are the two predicted breaks, user `mcp.types` imports and positional `McpError(ErrorData(...))` construction. Cross-version wire interop between a 3.4.3 peer and this branch is bidirectionally clean across 9 operations (3.4.3 client ↔ v4 server and v4 client ↔ 3.4.3 server over HTTP). All 29 `_ALIASES` bridge entries warn correctly with actionable messages.
1010

1111
## Environment
1212

@@ -68,7 +68,7 @@ async def read_schema():
6868
return tools[0].inputSchema # works, warns; prefer .input_schema
6969
```
7070

71-
The bridged fields are exactly those users read, data-driven from an `_ALIASES` table: `inputSchema`/`outputSchema` (Tool); `mimeType` (Resource, ResourceTemplate, TextResourceContents, BlobResourceContents, ImageContent, AudioContent) and `uriTemplate` (ResourceTemplate); `isError`/`structuredContent` (CallToolResult); `hasMore` (Completion); `serverInfo`/`protocolVersion` (InitializeResult); `nextCursor`/`resourceTemplates` (List\*Result); `systemPrompt`/`maxTokens`/`stopSequences`/`modelPreferences`/`toolChoice` (CreateMessageRequestParams); `requestedSchema` (ElicitRequestFormParams). WS2 verified all 25 alias entries warn correctly with actionable messages.
71+
The bridged fields are exactly those users read, data-driven from an `_ALIASES` table: `inputSchema`/`outputSchema` (Tool); `readOnlyHint`/`destructiveHint`/`idempotentHint`/`openWorldHint` (ToolAnnotations); `mimeType` (Resource, ResourceTemplate, TextResourceContents, BlobResourceContents, ImageContent, AudioContent) and `uriTemplate` (ResourceTemplate); `isError`/`structuredContent` (CallToolResult); `hasMore` (Completion); `serverInfo`/`protocolVersion` (InitializeResult); `nextCursor`/`resourceTemplates` (List\*Result); `systemPrompt`/`maxTokens`/`stopSequences`/`modelPreferences`/`toolChoice` (CreateMessageRequestParams); `requestedSchema` (ElicitRequestFormParams). WS2 verified all 29 alias entries warn correctly with actionable messages.
7272

7373
*Verify:* `fastmcp_slim/fastmcp/_compat.py` (the `_ALIASES` table and `install()`).
7474

docs/getting-started/upgrading/from-fastmcp-3.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async with Client("my_mcp_server.py") as client:
3131
schema = tools[0].inputSchema # still works, warns once
3232
```
3333

34-
Each bridged read emits a `FastMCPDeprecationWarning` pointing you at the snake_case name (`tools[0].input_schema` here). The bridge covers the fields users actually read: `inputSchema`/`outputSchema` on tools, `mimeType` on resources and content, `isError`/`structuredContent` on tool results, `nextCursor` on paginated results, `serverInfo`/`protocolVersion` on the initialize result, the sampling parameter fields (`systemPrompt`, `maxTokens`, `stopSequences`, `modelPreferences`, `toolChoice`), and `requestedSchema` on elicitation parameters.
34+
Each bridged read emits a `FastMCPDeprecationWarning` pointing you at the snake_case name (`tools[0].input_schema` here). The bridge covers the fields users actually read: `inputSchema`/`outputSchema` on tools; `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` on tool annotations; `mimeType` on resources and content; `isError`/`structuredContent` on tool results; `nextCursor` on paginated results; `serverInfo`/`protocolVersion` on the initialize result; the sampling parameter fields (`systemPrompt`, `maxTokens`, `stopSequences`, `modelPreferences`, `toolChoice`); and `requestedSchema` on elicitation parameters.
3535

3636
The bridge is controlled by the `mcp_camelcase_compat` setting, which defaults to on. Set it to `False` (or the environment variable `FASTMCP_MCP_CAMELCASE_COMPAT=false`) to turn the shims off, in which case only the snake_case names resolve:
3737

fastmcp_slim/fastmcp/_compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
"inputSchema": "input_schema",
4343
"outputSchema": "output_schema",
4444
},
45+
mcp_types.ToolAnnotations: {
46+
"readOnlyHint": "read_only_hint",
47+
"destructiveHint": "destructive_hint",
48+
"idempotentHint": "idempotent_hint",
49+
"openWorldHint": "open_world_hint",
50+
},
4551
mcp_types.Resource: {
4652
"mimeType": "mime_type",
4753
},

tests/test_compat.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ def test_tool_output_schema_bridged(self):
5151
with pytest.warns(FastMCPDeprecationWarning):
5252
assert tool.outputSchema == {"type": "string"} # ty: ignore[unresolved-attribute]
5353

54+
@pytest.mark.parametrize(
55+
("camel", "snake", "value"),
56+
[
57+
("readOnlyHint", "read_only_hint", True),
58+
("destructiveHint", "destructive_hint", False),
59+
("idempotentHint", "idempotent_hint", True),
60+
("openWorldHint", "open_world_hint", False),
61+
],
62+
)
63+
def test_tool_annotations_bridged(self, camel, snake, value):
64+
annotations = mcp_types.ToolAnnotations(**{snake: value})
65+
with pytest.warns(FastMCPDeprecationWarning):
66+
assert getattr(annotations, camel) is value
67+
5468
def test_call_tool_result_is_error_bridged(self):
5569
result = mcp_types.CallToolResult(content=[], is_error=True)
5670
with pytest.warns(FastMCPDeprecationWarning):

0 commit comments

Comments
 (0)