modelcontextprotocol / modelcontextprotocol/python-sdk
MCPServer unconditionally declares `prompts`, `resources`, `tools` capabilities on `initialize`
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 24.3k
- 分支
- 4k
- 平均合併
- 1 天 1 小時
- 30 天內合併 PR
- 31
描述
Pre-flight
- Using the latest MCP Python SDK (
v1.27.0) and also verified againstmain @ 3d7b311 - Searched existing issues and PRs — no duplicate found
Description
Reproduced against main @ 3d7b311 (2026-04-14).
Per schema/2025-11-25/schema.ts L388-L431, each entry in ServerCapabilities is documented as "Present if the server offers any [prompt templates | resources | tools]". lifecycle.mdx also requires both peers to only use capabilities that were successfully negotiated.
Current behavior is inconsistent with the documented schema semantics: MCPServer announces prompts, resources, and tools capabilities on every initialize response, regardless of whether any prompt / resource / tool has actually been registered. A server that only exposes one tool still advertises prompts and resources it doesn't have, and an entirely empty server advertises all three.
Root cause (current main):
src/mcp/server/mcpserver/server.py#L178-L184—MCPServer.__init__unconditionally wireson_list_tools=self._handle_list_tools,on_list_resources=...,on_list_prompts=...into the lowlevelServer(...).src/mcp/server/lowlevel/server.py#L205-L224—Server.__init__unconditionally populatesself._request_handlerswith"tools/list","resources/list","prompts/list"entries.src/mcp/server/lowlevel/server.py#L283-L328—get_capabilities()derives capability entries from key presence in_request_handlers, so all three are always included.
This behavior was carried over verbatim from FastMCP._setup_handlers() in the #1951 rename — the symptom pre-dates the refactor.
The maintainer TODO at lowlevel/server.py#L249-L253 (Rethink capabilities API ... Consider deriving capabilities entirely from server state) points to the same area — happy to align a fix with whatever direction you have in mind there.
Expected: capability entries should only appear when the corresponding primitive is actually offered (registered via decorator or add_*()).
Example Code
# empty_server.py
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("empty")
if __name__ == "__main__":
mcp.run()
# one_tool_server.py
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("hello")
@mcp.tool()
def echo(text: str) -> str:
return text
if __name__ == "__main__":
mcp.run()
# probe.py — sends initialize over stdio and prints the advertised capabilities
import json, subprocess, sys
for path in ["empty_server.py", "one_tool_server.py"]:
proc = subprocess.Popen([sys.executable, path],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
req = {"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-11-25","capabilities":{},
"clientInfo":{"name":"probe","version":"0"}}}
proc.stdin.write((json.dumps(req)+"\n").encode()); proc.stdin.flush()
print(path)
print(json.dumps(json.loads(proc.stdout.readline())["result"]["capabilities"], indent=2))
proc.terminate()
Actual output (both servers, identical):
{
"experimental": {},
"prompts": {"listChanged": false},
"resources": {"subscribe": false, "listChanged": false},
"tools": {"listChanged": false}
}
Expected:
empty_server.py→ noprompts,resources, ortoolsentries.one_tool_server.py→toolspresent,promptsandresourcesabsent.
Possible fixes (open to maintainer preference)
A. Filter capabilities in MCPServer based on manager state.
Post-process the ServerCapabilities returned by the lowlevel Server — omit tools / resources / prompts when the respective manager is empty. Minimal diff, keeps the decorator-after-init pattern. Could be done via a new optional capability_filter callable on the lowlevel Server, or via an override at the MCPServer layer.
B. Register list handlers lazily.
Pass on_list_tools=None etc. from MCPServer.__init__; register via the existing Server._add_request_handler on the first add_tool() / add_resource() / add_prompt(). Matches the documented "offers any X" schema semantics directly, no post-processing needed. This shifts one usage contract: primitives added via add_*() after run() would not appear in capabilities. In practice primitives are registered before run(), and post-handshake capability changes aren't supported by the spec regardless, so the regression surface is narrow — but it's worth calling out.
I'd like to take this on if you're open to a fix — please assign to me after triage, and let me know if you'd prefer a direction other than A or B.
Python & MCP Python SDK
Python 3.13
Reproduced on: v1.27.0 (released), main @ 3d7b311 (2026-04-14)
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
從 src/mcp/server/mcpserver/server.py 和 src/mcp/server/lowlevel/server.py 開始,重點關注 MCPServer 初始化、請求處理程式註冊和 get_capabilities()。使用 empty_server.py 和 one_tool_server.py 範例重現 initialize 回應,然後檢視低階 capabilities TODO。當 capabilities 反映實際註冊的原語,且範例產生預期的回應時,即表示完成。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- api, backend
- Issue 類型
- 缺陷
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 活躍度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 45/100