modelcontextprotocol / modelcontextprotocol/python-sdk

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

Đang mở
#2,473 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

bug P2
Ngôn ngữ chính
Python
Star
24.3k
Fork
4k
Merge trung bình
1 ngày 1 giờ
Pull request đã merge (30 ngày)
31

Mô tả

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)

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với src/mcp/server/mcpserver/server.py và src/mcp/server/lowlevel/server.py, đặc biệt là phần khởi tạo MCPServer, đăng ký trình xử lý yêu cầu và get_capabilities(). Tái hiện phản hồi initialize bằng các ví dụ empty_server.py và one_tool_server.py, sau đó xem xét TODO về capabilities ở mức thấp. Hoàn tất khi capabilities phản ánh các primitive thực sự được đăng ký và các ví dụ tạo ra những phản hồi mong đợi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
api, backend
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
45/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.