You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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):
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.
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.
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.
Activity
Ben-Home commentedon Oct 14, 2026
Worth flagging before someone picks option three:
Object.hasOwnguards in the duplicate check and the lookup are not sufficient on their own, because of the assignment path.this._registeredTools[name] = toolon 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 forObject.hasOwnturns today's loud throw into a silently corrupt registry for exactly that name.That is why
Object.create(null)or aMapis 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__, andhasOwnProperty; asserttools/listreturns all four andtools/calldispatches each, which also covers the lookup half that the current throw hides; plus one proxy-registration case, since a single upstream tool namedconstructortaking 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.
bunnysayzz commentedon Oct 17, 2026
this looks like a real bug. the problem is in packages/server/src/server/mcp.ts:
_registeredToolsis a plain object literal (line 124), so the duplicate check at line 1102 trips on inherited members likeconstructorandtoString, and registering__proto__at line 1024 mutates the prototype instead of setting an own property. initializing it withObject.create(null)fixes registration and all the lookup sites. happy to put up a PR if you can assign the issue to me.bunnysayzz commentedon Oct 17, 2026
Withdrawing my assignment request, #3040 already covers this with the same fix. Will leave it to that PR.
fix(server): use null-prototype maps for McpServer registries
fix(server): use null-prototype maps for McpServer registries
fix(server): use null-prototype maps for McpServer registries