Skip to content

sdk(server): bound request input size before Pydantic model_validate #239

@bokelley

Description

@bokelley

Context

Security review of PR #238 (typed handler params) flagged that create_tool_caller now runs Model.model_validate on every tool call with no input-size or depth cap at the SDK layer. Upstream transports (FastMCP streamable-HTTP, a2a-python) don't impose a uniform request-body cap, so a hostile caller can submit arbitrarily large JSON and force the server to parse it.

Pydantic 2's pydantic-core is Rust-native and resilient to stack depth, but:

  • Deeply-nested JSON with extra="allow" still allocates per-level Python objects.
  • Large string fields without a max_length constraint (e.g. promoted_offering on GetProductsRequest) become validator-free bulk.
  • CPU per-request rises non-trivially under adversarial input.

Pre-existing transport-level concern that PR #238 meaningfully increases because typed dispatch runs validation unconditionally when a model is declared.

Proposal

One of:

  1. Starlette middleware at bind time. _run_mcp_http / A2A bind sites install a max_body_size guard (Starlette has no built-in; needs a small custom ASGI middleware that reads Content-Length or buffers + caps). Default to a generous 10 MB, expose as a serve(..., max_request_size=) kwarg. Same knob for both transports.

  2. SDK-level len(json.dumps(params)) > N shortcut inside create_tool_caller before calling model_validate. Simpler, but re-serialises what the transport already parsed.

Prefer (1). It kicks in before parsing (cheaper) and covers non-typed handlers too.

Acceptance

  • Size cap on request bodies, enforced at the transport bind point.
  • Default documented in docs/handler-authoring.md.
  • Test: request exceeding the cap returns an appropriate transport error before dispatch runs.

Severity

Medium. Not a merge-blocker for anything shipped. Adversarial-load resilience for adopters running public-facing endpoints.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions