Repository navigation
fix(mcpserver): advertise capabilities only for registered primitives - #2499
Zelys-DFKH wants to merge 2 commits into
Conversation
MCPServer unconditionally passed non-None list handlers to the lowlevel Server, which caused it to advertise tools/resources/prompts capabilities even when none of those primitives had been registered. Per the MCP schema spec, a capability entry should only appear when the server actually offers that primitive. Adds a `capability_filter` hook to the lowlevel Server that, if set, post-processes the computed ServerCapabilities before they are returned. MCPServer uses this to suppress tools/resources/prompts entries when the corresponding manager is empty at capability-computation time (i.e. when create_initialization_options() is called, after all decorators have been applied). Fixes modelcontextprotocol#2473. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5bd1ab4 to
e187a74
Compare
… paths Lines 306-308 (match transport/stdio case) and 860-867 (run_stdio_async) are only covered via subprocess in test_1027_win_unreachable_cleanup.py. Coverage 7.10.7 (lowest-direct) does not capture subprocess coverage on Windows+Python 3.13, causing a spurious fail_under=100 failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment was marked as spam.
This comment was marked as spam.
|
You're right, and thanks for laying it out so clearly. The MUST prose settles it. And I half-saw this at the time, then talked myself out of it. In #2473 I waved off post- The two artifacts genuinely disagree, so I'd rather a maintainer called it. I'll rework it whichever way it lands. Needs a rebase regardless, so I'll wait on the shape. |
|
Thanks for the PR, and sorry it sat here without a proper review. We're closing most of the open PR backlog. v2 is out and changed a lot of the SDK, so many older PRs no longer apply as written, and we're a small team that realistically doesn't have the capacity to work through the rest. If this still matters to you on v2, the most useful thing you can do is open an issue (or comment on the existing one) with your use case and a repro. Hearing why it matters to you is what we use to decide what to prioritise. |
Summary
MCPServer.__init__unconditionally passes non-Nonelist handlers tothe lowlevel
Server, causingget_capabilities()to always advertisetools,resources, andprompts, even when none are registered.capability_filterparameter to the lowlevelServerthat post-processes the computed
ServerCapabilities.MCPServerpasses a filter that suppresses each entry when thecorresponding manager is empty at capability-computation time.
Motivation and Context
Per the MCP schema spec and lifecycle docs, a
ServerCapabilitiesentry should appear only when the server actually offers that primitive. Currently, an emptyMCPServeradvertises all three, and a server with only one tool still advertisesresourcesandpromptsit doesn't have.The filter runs when
create_initialization_options()is called at the start ofrun(), after all@mcp.tool()/@mcp.resource()/@mcp.prompt()decorators have been applied. Every transport path (stdio, SSE, streamable HTTP) callsserver.create_initialization_options()→get_capabilities(), so the filter applies without any per-transport changes.How Has This Been Tested?
Six new tests in
tests/server/mcpserver/test_server.pycover: empty server, tool-only, resource-only, resource-template-only, prompt-only, and all-registered cases.Full suite:
./scripts/test— 100% branch coverage.Breaking Changes
None. Servers that previously over-advertised will now send correct capability sets.
Types of changes
Checklist
Closes #2473