modelcontextprotocol / modelcontextprotocol/python-sdk

MCPServer unconditionally declares `prompts`, `resources`, `tools` capabilities on `initialize`

未关闭
#2,473 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

bug P2
主要语言
Python
星标
24.3k
派生
4k
平均合并
1 天 1 小时
30 天内合并 PR
31

描述

Pre-flight
  • Using the latest MCP Python SDK (v1.27.0) and also verified against main @ 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):

  1. src/mcp/server/mcpserver/server.py#L178-L184MCPServer.__init__ unconditionally wires on_list_tools=self._handle_list_tools, on_list_resources=..., on_list_prompts=... into the lowlevel Server(...).
  2. src/mcp/server/lowlevel/server.py#L205-L224Server.__init__ unconditionally populates self._request_handlers with "tools/list", "resources/list", "prompts/list" entries.
  3. src/mcp/server/lowlevel/server.py#L283-L328get_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 → no prompts, resources, or tools entries.
  • one_tool_server.pytools present, prompts and resources absent.
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)

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 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

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。