modelcontextprotocol / modelcontextprotocol/python-sdk
perf: JSONRPCMessage smart union scores all four branches on every inbound message
Nessuno ha ancora preso questa issue.
- Lingua principale
- Python
- Stelle
- 24.3k
- Fork
- 4k
- Merge medio
- 1g 1h
- PR unite (30g)
- 31
Descrizione
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)
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Inizia in src/mcp-types/mcp_types/jsonrpc.py, all’interno di jsonrpc_message_adapter, quindi esamina i punti di chiamata del transport indicati nell’issue per confermare che non siano necessarie modifiche. Esegui scripts/bench_jsonrpc_codec.py e i test di parità/branch proposti; il lavoro è completo quando sono preservati la validazione di una singola branch, il comportamento wire e la parità di classificazione, insieme al comportamento documentato per gli input non validi.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- python
- Ambito
- backend-api-design, performance
- Tipo di issue
- Refactoring
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Ferma
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 25/100