Skip to content

McpServer.registerTool rejects tool names inherited from Object.prototype (constructor, toString, __proto__) #3019

Open

Description

@ocdejong

McpServer stores its tools in a plain object (private _registeredTools: { [name: string]: RegisteredTool } = {}, mcp.ts:124). The duplicate check in registerTool (mcp.ts:1102) and the lookup in tools/call (mcp.ts:297) index that object by the tool name. A name that is inherited from Object.prototype therefore finds the inherited member, and registerTool throws Tool <name> is already registered on an empty server.

This matters for servers that proxy another server's tools: one upstream tool named constructor makes the proxy fail its whole tool registration.

Repro (@modelcontextprotocol/server 2.0.0, Node 24; the same code is in 2.3.1 at line 1096 and on main at 9bc40e9):

import { McpServer } from "@modelcontextprotocol/server";
const s = new McpServer({ name: "r", version: "0" });
for (const n of ["constructor", "toString", "__proto__", "hasOwnProperty"]) {
  try { s.registerTool(n, { description: "x" }, async () => ({ content: [] })); console.log(n, "ok"); }
  catch (e) { console.log(n, "->", e.message); }
}
constructor -> Tool constructor is already registered
toString -> Tool toString is already registered
__proto__ -> Tool __proto__ is already registered
hasOwnProperty -> Tool hasOwnProperty is already registered

Expected: each name registers, lists and calls like any other. These are valid tool names under the spec's naming rules.

Suggested fix: make the registries Maps, or create them with Object.create(null), or check Object.hasOwn in both registerTool and the tools/call lookup. _registeredPrompts, _registeredResources and _registeredResourceTemplates use the same pattern (mcp.ts:120-125), so they probably have the same problem.

Our workaround is to skip any upstream tool whose name is an own property of Object.prototype, and log a warning.

Activity

  1. Ben-Home commented on Oct 14, 2026

    @Ben-Home

    Worth flagging before someone picks option three: Object.hasOwn guards in the duplicate check and the lookup are not sufficient on their own, because of the assignment path. this._registeredTools[name] = tool on an object literal runs the inherited __proto__ setter when the name is __proto__, so instead of creating an own property the write attempts to replace the prototype (and silently does nothing when the value is not an object). A fix that only swaps the truthiness test for Object.hasOwn turns today's loud throw into a silently corrupt registry for exactly that name.

    That is why Object.create(null) or a Map is the right primitive here: with a null-prototype registry, __proto__ is an ordinary key and own-property semantics come for free on both the read and write paths. Same for the sibling registries (_registeredPrompts, _registeredResources, _registeredResourceTemplates) that share the pattern; a shared registry helper keeps them from drifting apart in future fixes.

    A test matrix built on your repro set: register constructor, toString, __proto__, and hasOwnProperty; assert tools/list returns all four and tools/call dispatches each, which also covers the lookup half that the current throw hides; plus one proxy-registration case, since a single upstream tool named constructor taking down the whole proxy listing is the practical failure mode.

    Your skip-and-warn workaround is the correct local mitigation until the registry is swapped.

  2. added
    v1Issues / PRs related to v1.x
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Oct 15, 2026
  3. bunnysayzz commented on Oct 17, 2026

    @bunnysayzz

    this looks like a real bug. the problem is in packages/server/src/server/mcp.ts: _registeredTools is a plain object literal (line 124), so the duplicate check at line 1102 trips on inherited members like constructor and toString, and registering __proto__ at line 1024 mutates the prototype instead of setting an own property. initializing it with Object.create(null) fixes registration and all the lookup sites. happy to put up a PR if you can assign the issue to me.

  4. bunnysayzz commented on Oct 17, 2026

    @bunnysayzz

    Withdrawing my assignment request, #3040 already covers this with the same fix. Will leave it to that PR.

  5. added 3 commits that reference this issue on Oct 22, 2026
    eee5be6
    509dc37
    a4a0a70
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions