modelcontextprotocol / modelcontextprotocol/typescript-sdk
McpServer constructor capabilities.tools.listChanged is silently overridden by setToolRequestHandlers()
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Summary
When constructing an McpServer with capabilities: { tools: { listChanged: false } }, the value passed to the constructor is silently overridden to true once any tool is registered. The listChanged: false setting never reaches the wire.
Steps to reproduce
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{ capabilities: { tools: { listChanged: false } } },
);
server.registerTool('my-tool', {
description: 'A tool',
inputSchema: { type: 'object', properties: {} },
}, async () => ({ content: [] }));
// Connect and send initialize — the response will contain:
// { capabilities: { tools: { listChanged: true } } }
// NOT { capabilities: { tools: { listChanged: false } } }
Root cause
McpServer.setToolRequestHandlers() (called on the first registerTool) unconditionally calls:
// dist/esm/server/mcp.js, lines 62-66
this.server.registerCapabilities({ tools: { listChanged: true } });
Server.registerCapabilities merges using mergeCapabilities which spreads additional over base:
result[k] = { ...baseValue, ...addValue }; // addValue wins
So { listChanged: false } (constructor) is overwritten by { listChanged: true } (SDK internal). The constructor option is ignored.
Workaround
After registering all tools but before connect():
server.server.registerCapabilities({ tools: { listChanged: false } });
await server.connect(transport);
This is fragile (accesses internal server property) and should not be necessary.
Expected behavior
The constructor option capabilities.tools.listChanged should take precedence over the hardcoded value in setToolRequestHandlers(). If explicitly set to false in the constructor, that value should be preserved. At minimum, the behavior should be documented.
SDK version
@modelcontextprotocol/sdk v1.29.0
Context
Discovered while building a stateless per-request server (sessionIdGenerator: undefined, enableJsonResponse: true) where notifications/tools/list_changed can never be delivered. Advertising listChanged: true is incorrect for this architecture. The behavior is harmless in that case, but the silent constructor override is a correctness issue for servers that genuinely need listChanged: false.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with setToolRequestHandlers() in dist/esm/server/mcp.js and the registerCapabilities merge behavior described in the issue. Trace how constructor capabilities reach the server, then verify that an explicit tools.listChanged: false remains false after registerTool(). Add or run coverage for the reported reproduction and confirm the initialize response preserves the constructor value.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100