modelcontextprotocol / modelcontextprotocol/python-sdk
perf: JSONRPCMessage smart union scores all four branches on every inbound message
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 24.3k
- 分支
- 4k
- 平均合併
- 1 天 1 小時
- 30 天內合併 PR
- 31
描述
Description
jsonrpc_message_adapter (src/mcp-types/mcp_types/jsonrpc.py) validates the
JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError union in
Pydantic's smart-union mode, which attempts/scores every branch for every message.
This adapter is the single decode choke point for every transport:
server/streamable_http.py(POST body),server/sse.py,server/stdio.pyclient/streamable_http.py(SSE data + JSON responses),client/sse.py,client/stdio.py
The four variants are trivially distinguishable by key presence per JSON-RPC 2.0
(method [+ valid id] → request/notification, error → error, result → response),
so a callable Discriminator + Tags (available at the repo's pydantic floor,
>=2.12.0) selects exactly one branch instead.
Measured impact
scripts/bench_jsonrpc_codec.py (proposed alongside the fix), darwin arm64 / CPython 3.14.2 / pydantic 2.12.5, best-of-5:
| payload | path | smart union | discriminator | speedup |
|---|---|---|---|---|
| request 109B | validate_json | 1.76µs | 1.05µs | 1.69x |
| request 109B | from_json + validate_python | 1.45µs | 0.90µs | 1.60x |
| notification 105B | validate_json | 1.88µs | 0.86µs | 2.18x |
| notification 105B | from_json + validate_python | 1.29µs | 0.80µs | 1.60x |
| result 22.3KB | validate_json | 51.7µs | 32.1µs | 1.61x |
| result 22.3KB | from_json + validate_python | 12.7µs | 12.2µs | 1.04x |
For high-throughput agent communication (many small requests/notifications per
second per session) this halves envelope-decode CPU on the hot path of every
transport.
Behavior compatibility
The fix keeps classification parity with today's smart-union outcomes for all
spec-valid messages, verified empirically and pinned by tests:
{"id": null | 1.5 | true, "method": ...}and absent-id →JSONRPCNotification(current downgrade behavior preserved; note the interaction with #2057 / PR #2075 - if that lands, the discriminator's id-guard becomes the single, clean place to implement the rejection)- degenerate
{method, id, result}→JSONRPCRequest;{id, result, error}→JSONRPCError JSONRPCMessagestays a plain union (soisinstance(x, JSONRPCMessage)keeps working); theAnnotated/Tagwrapping lives only inside theTypeAdapter- wire output (
dump_json,by_alias=True,exclude_none=True) is byte-identical - deliberate divergence on spec-invalid hybrids (surfaced by cubic's review on the PR):
{method, error}hybrids classify as a call instead ofJSONRPCError, and
{method: <non-str>, result}is rejected instead of falling through to
JSONRPCResponse- smart union picked those via field-count scoring, which let a
malformed frame masquerade as an error response to a pending request; pinned by tests - only visible change:
ValidationErrorfor unclassifiable input becomes a single
jsonrpc_message_invaliderror with a clear message instead of a 4-branch error dump
(no in-repo test asserts the old text; error codes and HTTP statuses unchanged)
Additional finding worth recording: with a callable discriminator,
validate_json must materialize the payload to call the discriminator, so the
server's existing two-phase parse (pydantic_core.from_json → validate_python)
is the faster path on large bodies (12.2µs vs 32.1µs on 22KB) - the fix adds a
comment pinning that so it doesn't get "cleaned up" into a regression later.
Proposed fix
PR ready: key-presence callable discriminator on the adapter, zero call-site
changes, new parity/branch tests (100% coverage maintained), standalone
micro-benchmark script.
Disclosure: this issue and the accompanying PR were developed with AI assistance
(Claude Code); all measurements and parity checks were run and verified locally.
References
- Draft PR with the implementation, measurements, and tests: #3135 (kept in draft pending this issue)
- Related: #2057 / #2075 (null-id classification - the discriminator id-guard is the natural locus for that change if it lands)
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
從 src/mcp-types/mcp_types/jsonrpc.py 中的 jsonrpc_message_adapter 開始,接著檢查 issue 中提到的 transport 呼叫位置,以確認不需要進行任何必要的變更。執行 scripts/bench_jsonrpc_codec.py 和提議的 parity/branch 測試;完成的標準是保留單一 branch 驗證、wire 行為和分類 parity,並符合針對無效輸入所記錄的行為。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- backend-api-design, performance
- Issue 類型
- 重構
- 難度
- 3/5
- 預估耗時
- 1-2 天
- 活躍度
- 停滯
- 描述清晰度
- 描述清楚
- 新手友好度
- 25/100