Fix docs/release follow-ups from the mcp-types package split (#2977)

This commit is contained in:
Max 2026-06-26 13:16:09 +02:00 • committed by GitHub
commit 3a8da8c0c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 71 additions and 35 deletions

View file

@ -12,6 +12,7 @@ on:
- docs_src/**
- mkdocs.yml
- src/mcp/**
- src/mcp-types/**
- scripts/build-docs.sh
- pyproject.toml
- uv.lock

View file

@ -58,3 +58,7 @@ jobs:
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
# Lets a re-run after a partially failed upload publish the remaining
# files instead of erroring on the ones already on PyPI.
skip-existing: true

View file

@ -35,6 +35,13 @@ jobs:
uv sync --group codegen --frozen
uv run --frozen --group codegen python scripts/gen_surface_types.py --check
# Resolves only mcp-types' declared dependencies into an empty environment,
# so an import of the SDK or anything from its stack fails here.
- name: mcp-types installs and imports standalone
run: |
uv run --isolated --no-project --with ./src/mcp-types python -c \
"import mcp_types, mcp_types.jsonrpc, mcp_types.methods, mcp_types.version, mcp_types.v2025_11_25, mcp_types.v2026_07_28"
# TODO(Max): Drop this in v2. Deliberate updates (e.g. the v2 status
# banner) go through the 'override-readme-freeze' label.
- name: Check README.md is not modified

View file

@ -18,7 +18,12 @@
## Package Management
- ONLY use uv, NEVER pip
- Installation: `uv add <package>`
- Installation: `uv add <package>`. Exception: the root project's runtime
dependencies are dynamic (the published `mcp` wheel exact-pins `mcp-types`),
so `uv add` cannot edit them — add the requirement to
`[tool.hatch.metadata.hooks.uv-dynamic-versioning].dependencies` in
`pyproject.toml` by hand, then run `uv lock`. Dependency groups, extras, and
the example packages still take plain `uv add`.
- Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't
rewrite `uv.lock` as a side effect.
- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run

View file

@ -2,7 +2,9 @@
## Bumping Dependencies
1. Change dependency version in `pyproject.toml`
1. Change the dependency version in `pyproject.toml`. The root `mcp` project's
runtime dependencies are dynamic and live under
`[tool.hatch.metadata.hooks.uv-dynamic-versioning].dependencies`.
2. Upgrade lock with `uv lock --resolution lowest-direct`
## Major or Minor Release
@ -21,6 +23,16 @@ The package version will be set automatically from the tag.
v2 pre-releases are cut from `main` with a PEP 440 pre-release tag: `v2.0.0aN`
for alphas, later `bN`/`rcN` for betas and release candidates.
A release publishes two distributions, `mcp` and `mcp-types`, at the same
version, and the `mcp` wheel exact-pins `mcp-types`. Before the first release
that includes both, the `mcp-types` PyPI project must be given the same
trusted publisher as `mcp` (this repository, workflow `publish-pypi.yml`,
environment `release`) and the same owners — without it the `mcp-types`
upload is rejected. If only some of the files upload, fix the cause and re-run
the publish job — `skip-existing` makes it skip whatever already landed. The
`Development Status` classifier in both `pyproject.toml` files is permanently
`5 - Production/Stable`; it is not bumped as part of any release.
1. Check the full test matrix is green on the release commit. The matrix runs
with `continue-on-error`, so a green workflow run does not mean the tests
passed — check the individual jobs.

View file

@ -9,27 +9,32 @@ nav = mkdocs_gen_files.Nav()
root = Path(__file__).parent.parent.parent
src = root / "src"
for path in sorted(src.rglob("*.py")):
module_path = path.relative_to(src).with_suffix("")
doc_path = path.relative_to(src).with_suffix(".md")
full_doc_path = Path("api", doc_path)
# `src/mcp-types` is a distribution directory, not an import package, so each
# package's dotted module path is taken relative to its own parent: deriving it
# from `src/` would emit the unimportable `mcp-types.mcp_types.*`.
for package in (src / "mcp", src / "mcp-types" / "mcp_types"):
base = package.parent
for path in sorted(package.rglob("*.py")):
module_path = path.relative_to(base).with_suffix("")
doc_path = path.relative_to(base).with_suffix(".md")
full_doc_path = Path("api", doc_path)
parts = tuple(module_path.parts)
parts = tuple(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")
elif parts[-1].startswith("_"):
continue
if parts[-1] == "__init__":
parts = parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")
elif parts[-1].startswith("_"):
continue
nav[parts] = doc_path.as_posix()
nav[parts] = doc_path.as_posix()
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
fd.write(f"::: {ident}")
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
fd.write(f"::: {ident}")
mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root))
mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root))
with mkdocs_gen_files.open("api/SUMMARY.md", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())

View file

@ -213,10 +213,11 @@ The WebSocket transport has been removed: `mcp.client.websocket.websocket_client
### `mcp.types` moved to the `mcp-types` package
The protocol wire types now live in a standalone distribution, `mcp-types`, imported as
`mcp_types`. It depends only on `pydantic`, so code that just needs to (de)serialize MCP
traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
`mcp_types`. Its only runtime dependencies are `pydantic` and `typing-extensions`, so code
that just needs to (de)serialize MCP traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
continues to re-export the type names at the top level, so `from mcp import Tool` is
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed.
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed. The
package's API reference is at [`mcp_types`](api/mcp_types/index.md).
**Why:** keeping the wire types in their own package lets tooling and lightweight clients
depend on the protocol schema without pulling in `httpx`, `starlette`, `uvicorn`, and the
@ -225,18 +226,18 @@ rest of the server/transport stack.
**Before (v1):**
```python
from mcp.types import Tool, CallToolResult
from mcp.types import Tool, Resource
from mcp.shared.version import LATEST_PROTOCOL_VERSION
```
**After (v2):**
```python
from mcp_types import Tool, CallToolResult
from mcp_types import Tool, Resource
from mcp_types.version import LATEST_PROTOCOL_VERSION
# Top-level re-exports are unchanged:
from mcp import Tool, CallToolResult
# Names `mcp` already re-exported at the top level are unchanged:
from mcp import Tool, Resource
```
### Removed type aliases and classes
@ -814,7 +815,7 @@ async def my_tool(ctx: Context[MyLifespanState]) -> str: ...
### Version constants
`SUPPORTED_PROTOCOL_VERSIONS` is deprecated — it's now the union of `HANDSHAKE_PROTOCOL_VERSIONS` (initialize-handshake versions) and `MODERN_PROTOCOL_VERSIONS` (per-request-envelope versions). If you were using it to mean "versions the initialize handshake accepts", switch to `HANDSHAKE_PROTOCOL_VERSIONS`. Named scalars derived from these tuples are now exported alongside them — `LATEST_HANDSHAKE_VERSION`, `LATEST_MODERN_VERSION`, `OLDEST_SUPPORTED_VERSION` — so prefer those over indexing the tuples directly.
`SUPPORTED_PROTOCOL_VERSIONS` is deprecated — it's now the union of `HANDSHAKE_PROTOCOL_VERSIONS` (initialize-handshake versions) and `MODERN_PROTOCOL_VERSIONS` (per-request-envelope versions). If you were using it to mean "versions the initialize handshake accepts", switch to `HANDSHAKE_PROTOCOL_VERSIONS`. Named scalars derived from these tuples are now exported alongside them — `LATEST_HANDSHAKE_VERSION`, `LATEST_MODERN_VERSION`, `OLDEST_SUPPORTED_VERSION` — so prefer those over indexing the tuples directly. All of these live in `mcp_types.version` (previously `mcp.shared.version`): `from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS`.
### `ProgressContext` and `progress()` context manager removed

View file

@ -136,7 +136,7 @@ markdown_extensions:
- sane_lists # this means you can start a list from any number
watch:
- src/mcp
- src
- docs_src
plugins:
@ -152,7 +152,7 @@ plugins:
- mkdocstrings:
handlers:
python:
paths: [src]
paths: [src, src/mcp-types]
options:
relative_crossrefs: true
members_order: source

View file

@ -3,7 +3,8 @@
JSON Schema files for each protocol version the SDK has a wire-shape surface
package for, vendored from the [spec repository] at the commit recorded in
`PINNED.json`. `scripts/gen_surface_types.py` reads these to regenerate
`src/mcp/types/v<version>/__init__.py`; CI runs the generator with `--check`.
`src/mcp-types/mcp_types/v<version>/__init__.py`; CI runs the generator with
`--check`.
To bump: drop the new `schema.json` here as `<protocol-version>.json`, update
the matching entry in `PINNED.json` (commit + sha256), and run

View file

@ -14,7 +14,7 @@ maintainers = [
keywords = ["mcp", "llm", "automation"]
license = { text = "MIT" }
classifiers = [
"Development Status :: 3 - Alpha",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",

View file

@ -0,0 +1,60 @@
"""Tests for the protocol-version registry and comparison helpers."""
import pytest
from mcp_types.version import (
HANDSHAKE_PROTOCOL_VERSIONS,
KNOWN_PROTOCOL_VERSIONS,
MODERN_PROTOCOL_VERSIONS,
is_version_at_least,
)
@pytest.mark.parametrize(
("version", "minimum", "expected"),
[
# equal
("2025-11-25", "2025-11-25", True),
("2024-11-05", "2024-11-05", True),
# above
("2025-11-25", "2025-06-18", True),
("2025-06-18", "2024-11-05", True),
# below
("2025-06-18", "2025-11-25", False),
("2024-11-05", "2025-03-26", False),
],
)
def test_is_version_at_least_ordering(version: str, minimum: str, expected: bool) -> None:
"""Known revisions order by registry position: equal, newer, and older pairs."""
assert is_version_at_least(version, minimum) is expected
@pytest.mark.parametrize("version", ["zzz", "", "2025-11-26", "draft", "9999-99-99"])
def test_is_version_at_least_unknown_version_is_false(version: str) -> None:
"""Unrecognized peer strings compare conservatively, never accidentally."""
assert is_version_at_least(version, "2024-11-05") is False
def test_is_version_at_least_unknown_minimum_raises() -> None:
"""An unknown minimum is programmer error, not peer input."""
with pytest.raises(ValueError, match="zzz"):
is_version_at_least("2025-11-25", "zzz")
@pytest.mark.parametrize(
("version", "minimum"), [(v, m) for v in KNOWN_PROTOCOL_VERSIONS for m in KNOWN_PROTOCOL_VERSIONS]
)
def test_is_version_at_least_matches_lexicographic_for_known_versions(version: str, minimum: str) -> None:
"""Drop-in equivalence: for every known (date-shaped) revision pair the helper
agrees with the string comparison it replaced."""
assert is_version_at_least(version, minimum) is (version >= minimum)
def test_supported_versions_are_known() -> None:
"""Every negotiable revision must be in the ordering registry."""
assert set(HANDSHAKE_PROTOCOL_VERSIONS) <= set(KNOWN_PROTOCOL_VERSIONS)
assert set(MODERN_PROTOCOL_VERSIONS) <= set(KNOWN_PROTOCOL_VERSIONS)
def test_known_versions_are_strictly_ordered() -> None:
"""The registry tuple is the ordering source of truth: ascending, no duplicates."""
assert list(KNOWN_PROTOCOL_VERSIONS) == sorted(set(KNOWN_PROTOCOL_VERSIONS))