Split protocol types into a standalone mcp-types package#2973
Conversation
Extract the wire types from `src/mcp/types/` into a separate distribution, `mcp-types` (imported as `mcp_types`), wired into the uv workspace. The new package depends only on pydantic, so tooling and lightweight clients can (de)serialize MCP traffic without the full server/transport stack. `mcp.shared.version` moves to `mcp_types.version` (a pure leaf with no `mcp` deps), which lets `mcp_types` depend on nothing in `mcp`. The `mcp.types` submodule and `mcp.shared.version` are removed; `mcp` re-exports the type names at the top level, so `from mcp import Tool` is unchanged. All importers are rewritten to `mcp_types`. Documented in docs/migration.md.
The client-conformance CI launches .github/actions/conformance/client.py, which still did `from mcp import types`; that name is gone after the split, so every scenario failed with ImportError. Use `import mcp_types as types`.
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="mcp-types/README.md">
<violation number="1" location="mcp-types/README.md:6">
P3: README dependency claim is incorrect; package also depends on `typing-extensions`. This can mislead lightweight consumers about install footprint.</violation>
</file>
<file name="examples/servers/simple-prompt/mcp_simple_prompt/server.py">
<violation number="1" location="examples/servers/simple-prompt/mcp_simple_prompt/server.py:3">
P2: Directly importing `mcp_types` without declaring `mcp-types` as a direct dependency creates undeclared runtime coupling. Add `mcp-types` to this example package dependencies.</violation>
</file>
Partial review: This PR has more than 50 files, so cubic reviewed the highest-priority files first. During the trial, paid plans get a higher file limit.
You can try an ultrareview to bypass the file limit, comment @cubic-dev-ai ultrareview. Learn more.
Fix all with cubic | Re-trigger cubic
| import anyio | ||
| import click | ||
| from mcp import types | ||
| import mcp_types as types |
There was a problem hiding this comment.
P2: Directly importing mcp_types without declaring mcp-types as a direct dependency creates undeclared runtime coupling. Add mcp-types to this example package dependencies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/servers/simple-prompt/mcp_simple_prompt/server.py, line 3:
<comment>Directly importing `mcp_types` without declaring `mcp-types` as a direct dependency creates undeclared runtime coupling. Add `mcp-types` to this example package dependencies.</comment>
<file context>
@@ -1,6 +1,6 @@
import anyio
import click
-from mcp import types
+import mcp_types as types
from mcp.server import Server, ServerRequestContext
</file context>
There was a problem hiding this comment.
Not applying. The examples use import mcp_types as types purely as the types namespace alias that replaced the old from mcp import types; they depend on the mcp SDK conceptually, and mcp pins mcp-types=={{ version }}, so it is always present. Declaring it across 13 unpublished demo packages is churn for no resolution benefit.
There was a problem hiding this comment.
The parent comment was wrong here: this alias is already covered by the pinned mcp -> mcp-types relationship, so adding direct dependencies to all 13 demo packages would be churn without benefit. I’ll drop this one.
| The wire types for the [Model Context Protocol](https://modelcontextprotocol.io). | ||
|
|
||
| This package holds the protocol message models, JSON-RPC envelope types, per-version | ||
| surface validators, and the protocol-version registry. It depends only on `pydantic`, |
There was a problem hiding this comment.
P3: README dependency claim is incorrect; package also depends on typing-extensions. This can mislead lightweight consumers about install footprint.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mcp-types/README.md, line 6:
<comment>README dependency claim is incorrect; package also depends on `typing-extensions`. This can mislead lightweight consumers about install footprint.</comment>
<file context>
@@ -0,0 +1,16 @@
+The wire types for the [Model Context Protocol](https://modelcontextprotocol.io).
+
+This package holds the protocol message models, JSON-RPC envelope types, per-version
+surface validators, and the protocol-version registry. It depends only on `pydantic`,
+so it can be installed on its own when you need to (de)serialize MCP traffic without
+pulling in the full `mcp` SDK.
</file context>
There was a problem hiding this comment.
Fixed in a6a3b86 - now states the runtime deps are pydantic and typing-extensions.
There was a problem hiding this comment.
Addressed in a6a3b86 — the README now lists both pydantic and typing-extensions.
Relocate the package from a repo-root `mcp-types/` to `src/mcp-types/`, mirroring how httpx2/httpcore2 nest each workspace member under src/. Update the workspace member path, pyright include, ruff per-file-ignore, coverage source + path mapping, and the codegen TYPES_DIR accordingly.
After the split, `uv build` only produced the root `mcp` distribution, so `mcp-types` would never reach PyPI. Build both packages explicitly (not `--all-packages`, which would also bundle the unpublishable examples/* members). Both share one git-tag-driven version via uv-dynamic-versioning, like httpx2.
Mirror the httpx2/httpcore2 setup: make mcp's dependencies dynamic and move
them into the uv-dynamic-versioning metadata hook so mcp-types can be pinned
with `mcp-types=={{ version }}`. Each mcp release now requires the exact
mcp-types built from the same commit. The workspace source keeps local/editable
installs resolving to the in-tree package regardless of the pin.
…g-unicorn # Conflicts: # tests/client/test_session.py
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pyproject.toml">
<violation number="1" location="pyproject.toml:3">
P3: Making `project.dependencies` dynamic breaks normal `uv add` workflows for runtime deps, creating a maintenance trap for contributors.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| [project] | ||
| name = "mcp" | ||
| dynamic = ["version"] | ||
| dynamic = ["version", "dependencies"] |
There was a problem hiding this comment.
P3: Making project.dependencies dynamic breaks normal uv add workflows for runtime deps, creating a maintenance trap for contributors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pyproject.toml, line 3:
<comment>Making `project.dependencies` dynamic breaks normal `uv add` workflows for runtime deps, creating a maintenance trap for contributors.</comment>
<file context>
@@ -1,6 +1,6 @@
[project]
name = "mcp"
-dynamic = ["version"]
+dynamic = ["version", "dependencies"]
description = "Model Context Protocol SDK"
readme = "README.v2.md"
</file context>
There was a problem hiding this comment.
Not applying: intentional. Making dependencies dynamic is what lets the uv-dynamic-versioning hook expand mcp-types=={{ version }} to the exact sibling version, exactly as httpx2 pins httpcore2. uv add still works - it edits the hook table and uv reconciles. The lockstep pin is worth the tradeoff.
There was a problem hiding this comment.
The parent comment was wrong here: dependencies is intentionally dynamic so uv-dynamic-versioning can expand mcp-types=={{ version }} to the exact sibling version, and uv add still reconciles correctly.
Force-include the repo-root LICENSE into the mcp-types sdist (it builds from src/mcp-types/, so it wasn't picked up), mirroring how httpx2 pulls the root LICENSE into each member. Remove the [tool.coverage.paths] mcp_types block: src/mcp-types/mcp_types lives under src/, so the existing 'source' alias already canonicalizes its local/CI/ Windows paths. Coverage still hits 100%.
|
From Claude: I went through this in depth — the diff, both built artifacts, the install paths CI doesn't exercise, and the docs/release plumbing that lives outside the PR's file set. The short version: the split itself is done right. A few things below need fixing, and the two most important ones are invisible to every green check on this PR because neither the publish path nor the docs build runs in PR CI. What I verified is solid, so nobody has to re-derive it:
Now the findings, in priority order. 1. The publish path can ship a globally broken
|
- README: mcp-types also depends on typing-extensions, not just pydantic. - migration.md: revert four 'Before (v1)' code blocks the bulk mcp.types -> mcp_types rewrite wrongly touched; v1 code imported from mcp.types.
What
Extract the protocol wire types from
src/mcp/types/into a separate distribution,mcp-types(imported asmcp_types), wired into the existing uv workspace as a member undersrc/mcp-types/(same nesting as httpx2/httpcore2).mcp-typesdepends only onpydantic+typing-extensions, so tooling and lightweight clients can (de)serialize MCP traffic without pulling inhttpx,starlette,uvicorn, and the rest of the server/transport stack.Key changes
src/mcp/types/→src/mcp-types/mcp_types/(_types,jsonrpc,_wire_base,methods,v2025_11_25/,v2026_07_28/), moved withgit mvso history is preserved.src/mcp/shared/version.py→src/mcp-types/mcp_types/version.py. It is a pure leaf with nomcpdependencies, which is what letsmcp_typesdepend on nothing inmcp(methods.pyneedsKNOWN_PROTOCOL_VERSIONS).mcp.typesandmcp.shared.versionare removed (no shim). All importers are rewritten tomcp_types/mcp_types.version.mcp.__init__still re-exports the type names frommcp_types, sofrom mcp import Toolis unchanged.[tool.uv.workspace]member,[tool.uv.sources],mcpdependency, pyrightinclude, ruff per-file-ignore, coveragesource+[tool.coverage.paths]mapping, andscripts/gen_surface_types.pyoutput path +--base-class.Breaking change
from mcp.types import Xandfrom mcp.shared.version import Xno longer work; usefrom mcp_types import X/from mcp_types.version import X. The top-levelfrom mcp import Xre-exports are unchanged. Documented indocs/migration.md.Verification
strict-no-coverclean.mcp-typesbuilds standalone (wheel includespy.typedand all modules).scripts/gen_surface_types.py --checkregenerates thev*validators identically from the new path.AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.