Skip to content

MCP tools silently swallow unknown option keys the schemas promise to reject #597

Description

@pbednarcik

Tool schemas declare additionalProperties: false, but nothing in the dispatch path
enforces it — request decoding reads the keys it knows (req.GetInt / req.GetString
with defaults) and unknown keys simply vanish. The call succeeds with default behavior
and no indication anything was ignored.

Observed shape (agent sessions on my production C# codebase): the agent passed
line_range: [120, 160] to read_file three times — a plausible guess for an option
that doesn't exist (the real window options are offset/limit) — and got the full
file back each time, silently. That's the heaviest tool in the surface paying maximum
token cost for a request that explicitly asked for a 40-line window, and the agent had
no signal to correct itself: no error, no warning, three identical retries. Verified
against current main: line_range appears nowhere in tools_fileops.go; the request
just carries an ignored key.

Why this class is worse than a normal typo hazard for agent callers specifically:

  • Agents learn option names from one tool's schema and transfer them to another
    (max_bytes is real on the budget layer, limit means different things on
    different tools). Cross-tool transfer of a key that doesn't exist on the target is
    routine, not exceptional.
  • The schema already promises rejection — additionalProperties: false is in the
    published contract. A client that trusts the contract assumes an accepted call meant
    every argument was understood.
  • The failure is invisible precisely where it's most expensive: oversized responses
    the server's own token accounting then attributes to the session.

Possible fix shapes, in order of strictness:

  1. Enforce the schema at dispatch: unknown key → tool error naming the unknown key and
    listing the valid ones. Matches the published contract; strictest for buggy clients
    that today "work" by accident.
  2. Warning rider: accept the call but attach _ignored_options: [...] to the result,
    the same pattern as _truncated_by_budget. Zero breakage; agents get the signal to
    self-correct on the next call.
  3. Either of the above gated by an env toggle during a transition.

Happy to implement whichever shape you prefer — the rider (2) looks like the natural
first step, with (1) as the eventual contract-honoring behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions