Skip to content

MCP: ToolAnnotations silently accepts misspelled hints #142

Description

@LukasGold

ToolAnnotations accepts a misspelled hint without error and silently discards
it, producing a tool whose annotations are quietly missing.

Verified against mcp 2.1.1 / mcp-types 2.1.1:

from mcp.types import ToolAnnotations

ToolAnnotations(readOnlyHnt=True).model_dump(exclude_none=True, by_alias=True)
# {}   <- no error, no warning, hint silently gone

Mechanism. Corrected 2026-08-28: the original report cited extra="allow" at
mcp/types/_types.py:1033, which does not match the version we use. There is no
explicit extra setting at all. The base class MCPModel
(mcp_types/_types.py:45) sets only:

model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)

so pydantic's default extra="ignore" applies and unknown keys are dropped
rather than stored. This is shared by all 189 protocol models in that module.
populate_by_name also means both readOnlyHint and read_only_hint are
accepted, which widens the surface a typo can land on.

The valid fields are title, read_only_hint, destructive_hint,
idempotent_hint, open_world_hint (mcp_types/_types.py:1365). 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.

Upstream state, searched 2026-08-28. No existing issue covers this:

Remaining action: file the upstream report (draft in the comment below).
Keep our guard test regardless.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions