ToolAnnotations in the MCP Python SDK is configured with extra="allow":
# mcp/types/_types.py:1033
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, extra="allow")
So a misspelled hint is accepted, stored as an extra field, and silently does nothing:
ToolAnnotations(readOnlyHnt=True) # no error, no effect
The valid fields are readOnlyHint, destructiveHint, idempotentHint, openWorldHint (_types.py:1365-1400). A wrong name produces a tool whose annotations are missing, with no warning at build time, no test failure, and no runtime symptom. The visible consequence is client-side: a read-only tool that is not marked read-only gets treated as mutating.
Our mitigation (in the shared-core refactor for #133): Operation carries four explicitly typed Optional[bool] fields rather than an open dict, and the adapter constructs ToolAnnotations by keyword, not **dict, so a wrong name is a TypeError. A test asserts every hint maps to a declared field.
Remaining action: report upstream at https://github.com/modelcontextprotocol/python-sdk, since extra="allow" on a fixed, spec-defined set of boolean hints trades a useful error for nothing. Keep our guard test regardless.
ToolAnnotationsin the MCP Python SDK is configured withextra="allow":So a misspelled hint is accepted, stored as an extra field, and silently does nothing:
The valid fields are
readOnlyHint,destructiveHint,idempotentHint,openWorldHint(_types.py:1365-1400). A wrong name produces a tool whose annotations are missing, with no warning at build time, no test failure, and no runtime symptom. The visible consequence is client-side: a read-only tool that is not marked read-only gets treated as mutating.Our mitigation (in the shared-core refactor for #133):
Operationcarries four explicitly typedOptional[bool]fields rather than an open dict, and the adapter constructsToolAnnotationsby keyword, not**dict, so a wrong name is aTypeError. A test asserts every hint maps to a declared field.Remaining action: report upstream at https://github.com/modelcontextprotocol/python-sdk, since
extra="allow"on a fixed, spec-defined set of boolean hints trades a useful error for nothing. Keep our guard test regardless.