When we shipped pyrpc mcp in v0.14.0, the first question from early adopters was why it lives behind uv add "pyrpc-core[mcp]" instead of being part of the default install. The short answer: the MCP SDK is heavy, pyRPC’s core value is RPC, and optional-to-default is additive while default-to-optional is breaking.
What the MCP SDK actually pulls in
Resolving mcp 2.1 as a dependency adds 18 transitive packages to the install tree. Most are lightweight, but several are not:
- cryptography, pulled by pyjwt’s crypto extra. The first compiled binary wheel in pyrpc-core’s entire dependency tree. Megabytes per platform, with a CVE-patch cadence every consumer inherits whether they need it or not.
- pydantic 2.12 minimum, which quietly raises the floor for every downstream package.
- starlette and sse-starlette, transport layers that the stdio server never touches.
- opentelemetry-api, the jsonschema family, httpx, and others that add surface area without adding value for projects that do not use AI agents.
pyRPC’s existing dependency set before the MCP extra is intentionally small. Adding the SDK would roughly triple the package count for a feature that most deployments never need.
The dependency tree comparison
pyrpc-core (default) pyrpc-core[mcp]
───────────────────── ─────────────────────
pydantic >=2.0 pydantic >=2.12 (raised floor)
typing-extensions typing-extensions
click click
rich rich
── added by mcp ──
mcp 2.1
├─ starlette
├─ sse-starlette
├─ opentelemetry-api
├─ httpx
├─ pydantic-extra-types
├─ pyjwt[crypto]
│ └─ cryptography (compiled binary)
├─ jsonschema
├─ httpcore
├─ anyio
├─ h11
├─ sniffio
├─ idna
├─ certifi
└─ ...pyRPC’s core value is RPC, not MCP
The package ships a type-safe RPC framework with code generation, framework adapters, and a CLI. Projects that install pyrpc-core need routers, schemas, and TypeScript output. They do not need a protocol server for AI clients. The MCP surface is a value-add for teams using AI coding tools, not a requirement for using pyRPC itself.
This is the same principle FastAPI applies with fastapi[standard], or SQLAlchemy with its async extras. The runtime library stays lean. The developer experience extras opt in.
The asymmetry that decided it
Promoting an optional extra to default later is purely additive. Nobody’s build breaks, adoption data justifies the weight, and the release notes carry good news. Demoting a default back to an extra is the opposite: it breaks every project that relied on plain installs providing the capability.
Starting conservative preserves the option to change direction. Starting bold forecloses retreat. The UX cost of the extra is one token in one install command. The config snippets for MCP clients are identical either way.
The thin CLI wrapper principle
pyrpc mcp does zero protocol work itself. It spawns a subprocess, passes the stdio file descriptors to the MCP SDK, and exits when the client disconnects. The entire command is a thin wrapper around process management. All the actual protocol handling lives in the mcp extra.
This means the default install can include the pyrpc mcp command in its help text, show a clear remediation message when the extra is missing, and never import anything from the SDK unless the user explicitly asks for it. The boundary is clean.
First-class despite optional
Optional must not mean second-class. The command appears in help regardless. The missing-dependency error prints the exact uv and pip commands to install the extra. The docs lead with the MCP setup. Tests always install the extra, so quality gates never weaken.
If adoption ever argues for promotion, the path is a minor release away. Additive, safe, and data-driven. That is exactly how dependency boundaries should work.

pyRPC