Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

SEP-2243 Mcp-Param-* validation runs a full `tools/list` per `tools/call`, with no opt-out and no by-name fast path

未關閉
#3,565 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
45/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
python
領域
api, backend

研究方向

The issue is in src/mcp/server/_streamable_http_modern.py, specifically the _tool_input_schema and _mcp_param_rejection functions. Start by reading the code around lines 264-362 to understand the validation flow. Run the minimal reproduction sketch to observe the behavior. Check related issues #3484 and #3513 for context. The fix involves adding a by-name resolution hook or an opt-out mechanism, requiring changes to the low-level Server class and the HTTP handling logic.

由索引模型根據 Issue 內容生成。

描述

spec-2026-07-28 v2

Summary

_mcp_param_rejection / _tool_input_schema in src/mcp/server/_streamable_http_modern.py (added by #3033, merged 2026-06-30) resolve a called tool's inputSchema by running the server's registered tools/list handler — through the full serve_one dispatch path (middleware, lifespans, up to _MCP_PARAM_LIST_PAGE_CAP = 100 pages) — before every tools/call that has non-empty arguments or any Mcp-Param-* header, on the 2026-07-28 protocol revision. This runs even when the tool being called advertises no x-mcp-header parameters at all, and there is no configuration to skip it. It is skipped only when app.get_request_handler("tools/list") is None or the call has neither arguments nor Mcp-Param-* headers (_mcp_param_rejection, lines 345–358).

For a server whose tools/list handler is itself expensive — an aggregator that fans a listing out to several backends, each requiring a per-user credential to enumerate — this turns every tool call into a full catalog refresh.

Where

  • src/mcp/server/_streamable_http_modern.py:
    • _tool_input_schema (lines 264–329): pages through serve_one(app, dctx, "tools/list", ...) to find one tool's schema.
    • _mcp_param_rejection (lines 332–362): the gate that calls it, pre-dispatch, for every tools/call with arguments or Mcp-Param-* headers.
    • handle_modern_request (line 438): calls _mcp_param_rejection before constructing the SSE deferral machinery.

PR #3033 acknowledges the cost

From the PR description:

The cost is an internal listing per validated tools/call: middleware, lifespans, and expensive/paginated tools/list handlers see extra invocations … optimizable later behind the same surface (e.g. a registry fast-path for the built-in handler).

A review comment on the PR (on src/mcp/server/_streamable_http_modern.py:399) separately flags that this validation runs pre-dispatch, before the SSE deferral/keepalive window exists:

In SSE mode (json_response=False), the new pre-dispatch Mcp-Param validation phase runs before the SSE deferral/keepalive machinery, so a 2026-07-28 tools/call writes no bytes to the wire while the internal tools/list schema walk runs (up to 100 paginated serve_one round trips). A deployment whose tools/list handler is slower than the upstream proxy's idle-read timeout previously worked (the keepalive committed within 15s of dispatch) but would now have every validated tools/call reset before dispatch — consider bounding the schema-resolving walk with a timeout that degrades to the existing logged fail-open skip.

That comment does not appear to have been acted on in 2.2.0 — _tool_input_schema has no timeout around the serve_one loop, only the page-count cap.

Aggregator impact (observed in production)

We run an MCP aggregating gateway (fastmcp 4.0.4 FastMCP with one ProxyProvider per backend MCP server, mounted via add_provider, ~7 backends, per-user credentials minted for backend requests). FastMCP's tools/list handler (AggregateProvider._list_tools) queries every provider's list_tools() in parallel; for a ProxyProvider this is a live upstream tools/list call, credential-minting included (see companion issue filed against PrefectHQ/fastmcp, which also has a caching gap on this path).

Observed:

  • One tools/call → 7 backend tools/list calls + 4 Kerberos ticket redemptions (credential minting triggered by listing certain backends).
  • A client batch of 10 parallel tools/calls → roughly 70 backend listings.

The call itself only ever needs one tool's schema. The server can resolve that by name — FastMCP.get_tool(name) reaches this without a full fan-out: AggregateProvider._get_tool still queries every child provider, but each namespaced child is wrapped in a Namespace transform whose get_tool short-circuits to None without calling the underlying provider when the name's prefix doesn't match (fastmcp/server/transforms/namespace.py), and the one matching ProxyProvider._get_tool reads from its own _tools_cache when fresh (cache_ttl, default 300s) instead of hitting the network. None of that machinery is reachable from _tool_input_schema, which only knows how to call the registered tools/list handler.

Proposed fixes

  1. By-name resolution hook. Let a lowlevel Server optionally accept a by-name schema resolver (e.g. an on_get_tool_schema(ctx, name) -> dict | None alongside on_list_tools) that _tool_input_schema prefers over the tools/list walk when present. MCPServer and downstream frameworks built on the lowlevel Server (e.g. fastmcp) can implement it against their own by-name lookup instead of paying for a full listing. Falls back to the existing tools/list walk when the hook is absent, so behavior for a bare Server with only a tools/list handler is unchanged.
  2. Skip when no tool declares x-mcp-header. A server could compute once (e.g. at the first tools/list, or lazily and cache) whether any registered tool actually declares an x-mcp-header-annotated property. If none do, no Mcp-Param-* header can ever be meaningfully violated for any tool, and the whole check — including the schema resolution — can be skipped server-wide. Note: MCPServer currently has no declaration/validation mechanism for x-mcp-header server-side (see #3484), so this flag would need to inspect raw inputSchema properties for the annotation key directly, not rely on a first-class registration API.
  3. At minimum, a documented, explicit opt-out (constructor flag or env var) for deployments that accept the compliance gap in exchange for not paying full-listing cost per call, given some servers' tools/list handlers are cheap and others' are not.

Minimal reproduction sketch

from mcp import types
from mcp.server.lowlevel.server import Server

list_calls = 0

async def on_list_tools(ctx, params):
    global list_calls
    list_calls += 1
    return types.ListToolsResult(tools=[...])  # one tool with an inputSchema

async def on_call_tool(ctx, params):
    return types.CallToolResult(content=[...])

server = Server("repro", on_list_tools=on_list_tools, on_call_tool=on_call_tool)

# Serve over Streamable HTTP and send one `tools/call` with non-empty
# `arguments` from a client on protocol 2026-07-28 (no tools/list sent by the
# client). Observe list_calls == 1: the server listed its whole catalog to
# validate a single call.

Related

  • #3484 — MCPServer has no x-mcp-header declaration or validation mechanism server-side; relevant to proposal (2) above, since there is currently no first-class way to ask "does any tool declare x-mcp-header" other than inspecting raw schemas.
  • #3513 — client-side analog: ClientSession.call_tool issues a tools/list after every tools/call when the output-schema cache is empty, with the same aggregator fan-out cost and no opt-out. Filed independently but the same underlying pattern (validation/caching bolted onto the request path via a full listing instead of a by-name lookup).
  • PR #3033 (merged) — the change this issue is about.
  • PR #4622 (PrefectHQ/fastmcp, merged) — confirms fastmcp's HTTP transport passes Mcp-Param-*/x-mcp-header through untouched; does not address the per-call listing cost.

🤖 Generated with Claude Code

主要語言
Python
星號
24.3k
分支
4k
平均合併
1 天 11 小時
30 天內合併 PR
30

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

modelcontextprotocol/python-sdk 的其他 Issue

查看 modelcontextprotocol/python-sdk 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。