modelcontextprotocol / modelcontextprotocol/python-sdk

perf: JSONRPCMessage smart union scores all four branches on every inbound message

Aperta
#3,136 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

enhancement needs decision P3 v1 v2
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.py
  • client/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
  • JSONRPCMessage stays a plain union (so isinstance(x, JSONRPCMessage) keeps working); the Annotated/Tag wrapping lives only inside the TypeAdapter
  • 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 of JSONRPCError, 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: ValidationError for unclassifiable input becomes a single
    jsonrpc_message_invalid error 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_jsonvalidate_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

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. 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

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.