Fix docs/release follow-ups from the mcp-types package split (#2977)
This commit is contained in:
parent
1b92efedc8
commit
3a8da8c0c3
11 changed files with 71 additions and 35 deletions
1
.github/workflows/deploy-docs.yml
vendored
1
.github/workflows/deploy-docs.yml
vendored
|
|
@ -12,6 +12,7 @@ on:
|
|||
- docs_src/**
|
||||
- mkdocs.yml
|
||||
- src/mcp/**
|
||||
- src/mcp-types/**
|
||||
- scripts/build-docs.sh
|
||||
- pyproject.toml
|
||||
- uv.lock
|
||||
|
|
|
|||
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
7
.github/workflows/shared.yml
vendored
7
.github/workflows/shared.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
14
RELEASE.md
14
RELEASE.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
60
tests/types/test_version.py
Normal file
60
tests/types/test_version.py
Normal 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))
|
||||
Loading…
Reference in a new issue