modelcontextprotocol / modelcontextprotocol/python-sdk

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

Abierto
#3,136 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

enhancement needs decision P3 v1 v2
Lenguaje dominante
Python
Estrellas
24.3k
Forks
4k
Merge medio
1 d 1 h
PR fusionados (30 d)
31

Descripción

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)

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Empieza en src/mcp-types/mcp_types/jsonrpc.py, en jsonrpc_message_adapter, y revisa después los puntos de llamada del transporte mencionados en el issue para confirmar que no se requieren cambios. Ejecuta scripts/bench_jsonrpc_codec.py y las pruebas de paridad/branch propuestas; se considera completado cuando la validación de una sola branch, el comportamiento wire y la paridad de clasificación se mantienen, junto con el comportamiento documentado para entradas no válidas.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
backend-api-design, performance
Tipo de issue
Refactorización
Dificultad
3/5
Tiempo estimado
1-2 días
Estado de actividad
Estancado
Claridad
Bien especificado
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.