Skip to content

The MCP tool bridge asserts openWorldHint: false on every bridged tool from no declared source #13350

Description

@os-trump

Found while fixing #13318 (the same annotations object literal, a different hint). Filed rather than folded in: #13318's scope is the two safety hints that had a declared source waiting to be read, and this one has no correct shape that measurement can settle, so it needs a decision rather than a patch.

Measured at d571c53f1

packages/mcp/src/mcp-server-runtime.ts, registerToolFromDefinition, after #13318:

annotations: {
...safetyAnnotations(tool),openWorldHint: false,},

safetyAnnotations now reads what the definition declares and omits what it cannot source. openWorldHint: false sits beside it, asserted unconditionally for every bridged tool — every tool an app registers under its own name included — from nothing at all. AIToolDefinition has no member expressing it (name, label, description, parameters, category, outputSchema, objectName, requiresConfirmation), and git grep -n openWorldHint -- 'packages/**' finds it only at annotation-writing sites, never at a site that derives it.

That is the same class as the defect #13318 describes: a hint presented as a property of the tool that is really a property of this file. An app can register a tool that calls a weather API, an LLM, or any outbound service, and this bridge tells every MCP client its domain of interaction is closed.

Why it was not folded into the #13318 fix

Because the repair is not mechanical, and the two obvious moves are both wrong in a different direction:

  • Omitting it is what the sibling hints now do, but @modelcontextprotocol/sdk 1.30.0 documents openWorldHint as Default: **true** (ToolAnnotationsSchema in dist/esm/types.js). Dropping the assertion therefore flips every conforming host to assuming an OPEN world for tools that are mostly closed — a change away from the accurate answer for the common case, not toward it.
  • Keeping it is the status quo: accurate for the platform's own data/metadata tools, and a false claim for anything an app registers that reaches outside.

The mcp-http-tools.ts bridge already shows the distinction is real and authorable in principle: it asserts openWorldHint: true for run_action (its comment: actions run app-defined business logic with side effects, writes, flows, outbound calls) and false for the object-CRUD reads. Nothing carries that distinction across the AIToolDefinition seam.

What a fix would need to decide

  1. Whether AIToolDefinition should gain a declared signal (and whether an action-backed tool should inherit run_action's openWorldHint: true, which would be the consistent reading of the existing comment).
  2. Failing that, whether asserting false for platform-registered names only — the fallback shape The MCP tool bridge derives destructiveHint / readOnlyHint from a hardcoded tool-name set, so every app-registered and action-backed tool is advertised as neither read-only nor destructive #13318 left in place for readOnlyHint — is better than asserting it for everyone, given the protocol default is true and omission is therefore not the conservative direction here.
  3. Whether the hint is worth a contract member at all, or should simply be dropped from this bridge with the protocol default accepted.

⛔ Not a re-litigation of anything: no ruling covers openWorldHint, and #13318 deliberately left it untouched and said so in its PR body.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions