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/** - docs_src/**
- mkdocs.yml - mkdocs.yml
- src/mcp/** - src/mcp/**
- src/mcp-types/**
- scripts/build-docs.sh - scripts/build-docs.sh
- pyproject.toml - pyproject.toml
- uv.lock - uv.lock

View file

@ -58,3 +58,7 @@ jobs:
- name: Publish package distributions to PyPI - name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 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 sync --group codegen --frozen
uv run --frozen --group codegen python scripts/gen_surface_types.py --check 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 # TODO(Max): Drop this in v2. Deliberate updates (e.g. the v2 status
# banner) go through the 'override-readme-freeze' label. # banner) go through the 'override-readme-freeze' label.
- name: Check README.md is not modified - name: Check README.md is not modified

View file

@ -18,7 +18,12 @@
## Package Management ## Package Management
- ONLY use uv, NEVER pip - 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 - Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't
rewrite `uv.lock` as a side effect. rewrite `uv.lock` as a side effect.
- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run - Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run

View file

@ -2,7 +2,9 @@
## Bumping Dependencies ## 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` 2. Upgrade lock with `uv lock --resolution lowest-direct`
## Major or Minor Release ## 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` 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. 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 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 with `continue-on-error`, so a green workflow run does not mean the tests
passed — check the individual jobs. passed — check the individual jobs.

View file

@ -9,27 +9,32 @@ nav = mkdocs_gen_files.Nav()
root = Path(__file__).parent.parent.parent root = Path(__file__).parent.parent.parent
src = root / "src" src = root / "src"
for path in sorted(src.rglob("*.py")): # `src/mcp-types` is a distribution directory, not an import package, so each
module_path = path.relative_to(src).with_suffix("") # package's dotted module path is taken relative to its own parent: deriving it
doc_path = path.relative_to(src).with_suffix(".md") # from `src/` would emit the unimportable `mcp-types.mcp_types.*`.
full_doc_path = Path("api", doc_path) 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__": if parts[-1] == "__init__":
parts = parts[:-1] parts = parts[:-1]
doc_path = doc_path.with_name("index.md") doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md") full_doc_path = full_doc_path.with_name("index.md")
elif parts[-1].startswith("_"): elif parts[-1].startswith("_"):
continue continue
nav[parts] = doc_path.as_posix() nav[parts] = doc_path.as_posix()
with mkdocs_gen_files.open(full_doc_path, "w") as fd: with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts) ident = ".".join(parts)
fd.write(f"::: {ident}") 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: with mkdocs_gen_files.open("api/SUMMARY.md", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav()) 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 ### `mcp.types` moved to the `mcp-types` package
The protocol wire types now live in a standalone distribution, `mcp-types`, imported as 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 `mcp_types`. Its only runtime dependencies are `pydantic` and `typing-extensions`, so code
traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and 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 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 **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 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):** **Before (v1):**
```python ```python
from mcp.types import Tool, CallToolResult from mcp.types import Tool, Resource
from mcp.shared.version import LATEST_PROTOCOL_VERSION from mcp.shared.version import LATEST_PROTOCOL_VERSION
``` ```
**After (v2):** **After (v2):**
```python ```python
from mcp_types import Tool, CallToolResult from mcp_types import Tool, Resource
from mcp_types.version import LATEST_PROTOCOL_VERSION from mcp_types.version import LATEST_PROTOCOL_VERSION
# Top-level re-exports are unchanged: # Names `mcp` already re-exported at the top level are unchanged:
from mcp import Tool, CallToolResult from mcp import Tool, Resource
``` ```
### Removed type aliases and classes ### Removed type aliases and classes
@ -814,7 +815,7 @@ async def my_tool(ctx: Context[MyLifespanState]) -> str: ...
### Version constants ### 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 ### `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 - sane_lists # this means you can start a list from any number
watch: watch:
- src/mcp - src
- docs_src - docs_src
plugins: plugins:
@ -152,7 +152,7 @@ plugins:
- mkdocstrings: - mkdocstrings:
handlers: handlers:
python: python:
paths: [src] paths: [src, src/mcp-types]
options: options:
relative_crossrefs: true relative_crossrefs: true
members_order: source members_order: source

View file

@ -3,7 +3,8 @@
JSON Schema files for each protocol version the SDK has a wire-shape surface 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 package for, vendored from the [spec repository] at the commit recorded in
`PINNED.json`. `scripts/gen_surface_types.py` reads these to regenerate `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 To bump: drop the new `schema.json` here as `<protocol-version>.json`, update
the matching entry in `PINNED.json` (commit + sha256), and run the matching entry in `PINNED.json` (commit + sha256), and run

View file

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