modelcontextprotocol/typescript-sdk

McpServer re-registers capabilities after connect, blocking dynamic registration even when capabilities were supplied at construction

Opened this issue · 1 comments

Summary

When creating McpServer with ServerOptions.capabilities that include resources and tools, calling registerResource or registerTool after connect() fails with “Cannot register capabilities after connecting to transport”. The high-level server re-calls server.registerCapabilities(...) the first time handlers are installed, even post-connect.

Steps to Reproduce

  1. Create McpServer with capabilities:
const capabilities = {
  resources: { subscribe: true, listChanged: true },
  tools: { listChanged: true },
};
const server = new McpServer({ name: "demo", version: "1.0.0" }, { capabilities });
  1. await server.connect(transport);
  2. After connect, call server.registerResource(...) (or server.registerTool(...))
  3. Observe HTTP 500 with error “Cannot register capabilities after connecting to transport”

Expected Behavior

  • If capabilities were provided at construction, registering resources/tools/prompts after connect should work. The server should not attempt to re-register capabilities post-connect.

Actual Behavior

  • First registration after connect triggers handler initialization in McpServer, which unconditionally calls server.registerCapabilities(...). Since Server.registerCapabilities forbids post-connect, it throws.

Notes

  • Calls occur in typescript-sdk/src/server/mcp.ts within:
    • setResourceRequestHandlers()this.server.registerCapabilities({ resources: { listChanged: true } })
    • setToolRequestHandlers()this.server.registerCapabilities({ tools: { listChanged: true } })
    • setPromptRequestHandlers()this.server.registerCapabilities({ prompts: { listChanged: true } })
  • Guard that throws is in typescript-sdk/src/server/index.ts registerCapabilities().

Proposed Fix

  • Make this.server.registerCapabilities(...) idempotent if capabilities are already registered.

Environment

  • @modelcontextprotocol/sdk 1.17.3
  • Transport: Streamable HTTP (client error surfaced as HTTP 500)

@inverted-capital I faced the same issue when working on a project that needed to create tools/resources/prompts at runtime.
The workaround I used was to register dummies (1 of each type of tool/resource/prompt) before a transport is established. This seemed to enable the capability for each type in the SDK permitting later things to be registered even after connection. NOTE: They can even be removed after connection and things still work - though since they are disabled, it is fine to keep them around until other tools/resources/prompts are registered.

It is not ideal but its working well for me.