Promote the v2 README to README.md ahead of the first v2 beta (#3014)

This commit is contained in:
Max
2026-06-29 13:01:54 +02:00
committed by GitHub
parent f664db8952
commit f2e63c979a
12 changed files with 125 additions and 2724 deletions
+1 -16
View File
@@ -27,8 +27,6 @@ jobs:
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
with:
extra_args: --all-files --verbose
env:
SKIP: no-commit-to-branch,readme-v1-frozen
- name: Surface types match vendored schema
run: |
@@ -42,19 +40,6 @@ jobs:
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
if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'override-readme-freeze')
run: |
git fetch --no-tags --depth=1 origin "$BASE_SHA"
if git diff --name-only "$BASE_SHA" -- README.md | grep -q .; then
echo "::error::README.md is frozen at v1. Edit README.v2.md instead."
exit 1
fi
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
test:
name: test (${{ matrix.python-version }}, ${{ matrix.dep-resolution.name }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
@@ -116,7 +101,7 @@ jobs:
run: uv sync --frozen --all-extras --python 3.10
- name: Check README snippets are up to date
run: uv run --frozen scripts/update_readme_snippets.py --check --readme README.v2.md
run: uv run --frozen scripts/update_readme_snippets.py --check
# `mkdocs.yml` sets `strict: true` and `pymdownx.snippets: check_paths: true`,
# but until this job existed the docs were only ever built post-merge by
+1 -8
View File
@@ -42,7 +42,6 @@ repos:
types: [python]
language: system
pass_filenames: false
exclude: ^README(\.v2)?\.md$
- id: pyright
name: pyright
entry: uv run --frozen pyright
@@ -55,15 +54,9 @@ repos:
language: system
files: ^(pyproject\.toml|uv\.lock)$
pass_filenames: false
# TODO(Max): Drop this in v2.
- id: readme-v1-frozen
name: README.md is frozen (v1 docs)
entry: README.md is frozen at v1. Edit README.v2.md instead.
language: fail
files: ^README\.md$
- id: readme-snippets
name: Check README snippets are up to date
entry: uv run --frozen python scripts/update_readme_snippets.py --check
language: system
files: ^(README\.v2\.md|docs_src/.*\.py|examples/.*\.py|scripts/update_readme_snippets\.py)$
files: ^(README\.md|docs_src/.*\.py|scripts/update_readme_snippets\.py)$
pass_filenames: false
+1 -2
View File
@@ -12,8 +12,7 @@
shim) must be documented in `docs/migration.md`.
- `v1.x` is the release branch for the current stable line. Backport PRs target
this branch and use a `[v1.x]` title prefix.
- `README.md` is frozen at v1 (a pre-commit hook rejects edits). Edit
`README.v2.md` instead.
- `README.md` documents v2. The v1 README lives on the `v1.x` branch.
## Package Management
+1 -1
View File
@@ -105,7 +105,7 @@ uv run ruff check .
uv run ruff format .
```
7. Update README snippets if you modified example code:
7. Update README snippets if you modified `docs_src/` code embedded in the README:
```bash
uv run scripts/update_readme_snippets.py
+98 -2540
View File
File diff suppressed because it is too large Load Diff
-132
View File
@@ -1,132 +0,0 @@
# MCP Python SDK
<div align="center">
<strong>Python implementation of the Model Context Protocol (MCP)</strong>
[![PyPI][pypi-badge]][pypi-url]
[![MIT licensed][mit-badge]][mit-url]
[![Python Version][python-badge]][python-url]
[![Documentation][docs-badge]][docs-url]
[![Protocol][protocol-badge]][protocol-url]
[![Specification][spec-badge]][spec-url]
</div>
<!-- TODO(v2): Move this content back to README.md when v2 is released -->
> **Important: this documents v2 of the SDK, which is in alpha.** Pre-releases are published to PyPI as `2.0.0aN`, and each alpha may contain breaking changes from the previous one.
>
> v2 is a major rework of the SDK, both to support the [2026-07-28 MCP specification release](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) and to fix long-standing architectural issues. See the [migration guide](https://py.sdk.modelcontextprotocol.io/v2/migration/) for what's changed. We're targeting a beta on 2026-06-30 and a stable v2 on 2026-07-27, alongside the spec release. Before stable, we plan to add a significant set of backwards compatibility shims so the final upgrade is much smaller than today's diff.
>
> **v1.x is the only stable release line and remains recommended for production.** It is in maintenance mode and continues to receive critical bug fixes and security patches. Installers never select a pre-release unless you opt in (for example `pip install mcp==2.0.0a3`), so existing installs are unaffected. **If your package depends on `mcp`, add a `<2` upper bound to your version constraint (for example `mcp>=1.27,<2`) before the stable release lands.**
>
> Try the alpha and tell us what breaks: [#python-sdk-dev on the MCP Contributors Discord](https://discord.gg/6CSzBmMkjX). For v1 documentation, see [the v1.x README](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/README.md).
## Documentation
**The documentation lives at <https://py.sdk.modelcontextprotocol.io/v2/>.**
It has the full [tutorial](https://py.sdk.modelcontextprotocol.io/v2/tutorial/), the [API reference](https://py.sdk.modelcontextprotocol.io/v2/api/mcp/), and the [migration guide](https://py.sdk.modelcontextprotocol.io/v2/migration/).
## What is MCP?
The [Model Context Protocol](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but designed for LLM interactions. With this SDK you can:
- **Build MCP servers** that expose tools, resources, and prompts to any MCP host
- **Build MCP clients** that connect to any MCP server
- Speak every standard transport: stdio, Streamable HTTP, and SSE
## Requirements
Python 3.10+.
## Installation
```bash
uv add "mcp[cli]==2.0.0a3" # or: pip install "mcp[cli]==2.0.0a3"
```
The pin matters while v2 is in pre-release: an unpinned install resolves to the latest stable v1.x, which this README does not describe. Check [PyPI](https://pypi.org/project/mcp/#history) for the newest pre-release, and use `uv run --with "mcp==2.0.0a3"` for one-off commands.
## A server in 15 lines
Create a `server.py`:
<!-- snippet-source docs_src/index/tutorial001.py -->
```python
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
```
_Full example: [docs_src/index/tutorial001.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs_src/index/tutorial001.py)_
<!-- /snippet-source -->
That's a complete MCP server: one tool, one templated resource. Open it in the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
```bash
uv run mcp dev server.py
```
Call `add` with `a=1`, `b=2` and you get `3` back.
Notice what you did **not** write: no JSON Schema (`a: int, b: int` _is_ the schema), no request parsing, no validation code, no protocol handling. Two type-hinted Python functions and a docstring.
[The tutorial](https://py.sdk.modelcontextprotocol.io/v2/tutorial/) takes it from here.
## A client in 10 lines
The same package is a full MCP **client**. `Client` connects to a URL, a stdio subprocess, a custom transport, or (for tests) straight to a server object in memory with no transport at all:
```python
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
print(result.structured_content) # {'result': 3}
asyncio.run(main())
```
Swap `mcp` for `"http://localhost:8000/mcp"` and the exact same code talks to a remote server.
## Contributing
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](https://github.com/modelcontextprotocol/python-sdk/blob/main/CONTRIBUTING.md) to get started.
## License
This project is licensed under the MIT License. See the [LICENSE](https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE) file for details.
[pypi-badge]: https://img.shields.io/pypi/v/mcp.svg
[pypi-url]: https://pypi.org/project/mcp/
[mit-badge]: https://img.shields.io/pypi/l/mcp.svg
[mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE
[python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg
[python-url]: https://www.python.org/downloads/
[docs-badge]: https://img.shields.io/badge/docs-python--sdk-blue.svg
[docs-url]: https://py.sdk.modelcontextprotocol.io/v2/
[protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg
[protocol-url]: https://modelcontextprotocol.io
[spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg
[spec-url]: https://modelcontextprotocol.io/specification/latest
+11 -5
View File
@@ -33,11 +33,17 @@ 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. Update the pre-release version examples in `README.md` and the docs
(grep the outgoing version — the pins live in the README Installation
section, `docs/index.md`, and `docs/installation.md`) so the tagged
commit — and therefore the README PyPI publishes — names the version
being released. When entering a new phase (alpha → beta → rc), update
the banner wording too.
2. 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.
2. Create the release as a pre-release, passing the exact commit verified in
step 1 as `--target` (otherwise the tag is created from whatever `main`'s
3. Create the release as a pre-release, passing the exact commit verified in
step 2 as `--target` (otherwise the tag is created from whatever `main`'s
HEAD is by then). The tagged commit determines everything about the
release — the workflows that run and the package metadata (readme,
classifiers) that gets published — so it must contain the current release
@@ -50,13 +56,13 @@ the publish job — `skip-existing` makes it skip whatever already landed. The
gh release create v2.0.0aN --prerelease --title v2.0.0aN --target <commit-sha>
```
3. Curate the release notes instead of relying on auto-generated ones: what
4. Curate the release notes instead of relying on auto-generated ones: what
changed since the previous pre-release, what is known-incomplete, the
install line (`pip install mcp==2.0.0aN`), and a link to the migration
guide. Use the absolute URL
(`https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md`)
because relative links don't resolve in GitHub release bodies.
4. If a pre-release turns out to be broken, yank it on PyPI and cut the next
5. If a pre-release turns out to be broken, yank it on PyPI and cut the next
one. Never delete a release from PyPI — version numbers cannot be reused.
Yanking doesn't stop `==` pins from installing the broken version, so set
the yank reason (and edit the GitHub release notes) to point at the
+5 -5
View File
@@ -4,9 +4,9 @@
example per protocol feature, each with its own README. Start with
[`stories/tools/`](stories/tools/); the [stories README](stories/README.md)
has the full table and how to run them.
- [`snippets/`](snippets/) — short extracts embedded into `README.v2.md`. Kept
minimal and in sync with the top-level README; not intended to be run
standalone.
- [`snippets/`](snippets/) — short extracts that were embedded into the v1
README (now on the `v1.x` branch); superseded by `docs_src/`, which the docs
and README embed today. Retained pending consolidation into `stories/`.
- [`servers/everything-server/`](servers/everything-server/) — the conformance
target for the cross-SDK
[conformance suite](https://github.com/modelcontextprotocol/conformance).
@@ -15,8 +15,8 @@
migration guide; superseded by `stories/` and slated for removal.
- [`clients/`](clients/) and the remaining [`servers/`](servers/) directories
(`simple-*`, `sse-polling-demo`, `structured-output-lowlevel`) — standalone
v1-era projects still linked from `README.v2.md`; retained pending
consolidation into `stories/`.
v1-era projects retained pending consolidation into `stories/` (the
`simple-auth` pair is still linked from `docs/advanced/`).
For real-world servers see the
[servers repository](https://github.com/modelcontextprotocol/servers).
+1 -2
View File
@@ -2,7 +2,7 @@
name = "mcp"
dynamic = ["version", "dependencies"]
description = "Model Context Protocol SDK"
readme = "README.v2.md"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
maintainers = [
@@ -175,7 +175,6 @@ executionEnvironments = [
[tool.ruff]
line-length = 120
target-version = "py310"
extend-exclude = ["README.md", "README.v2.md"]
[tool.ruff.lint]
select = [
+3 -5
View File
@@ -93,16 +93,16 @@ def process_snippet_block(match: re.Match[str], check_mode: bool = False) -> str
return full_match
def update_readme_snippets(readme_path: Path = Path("README.md"), check_mode: bool = False) -> bool:
def update_readme_snippets(check_mode: bool = False) -> bool:
"""Update code snippets in README.md with live code from source files.
Args:
readme_path: Path to the README file
check_mode: If True, only check if updates are needed without modifying
Returns:
True if file is up to date or was updated, False if check failed
"""
readme_path = Path("README.md")
if not readme_path.exists():
print(f"Error: README file not found: {readme_path}")
return False
@@ -146,12 +146,10 @@ def main():
parser.add_argument(
"--check", action="store_true", help="Check mode - verify snippets are up to date without modifying"
)
# TODO(v2): Drop the `--readme` argument when v2 is released, and set to `README.md`.
parser.add_argument("--readme", default="README.v2.md", help="Path to README file (default: README.v2.md)")
args = parser.parse_args()
success = update_readme_snippets(Path(args.readme), check_mode=args.check)
success = update_readme_snippets(check_mode=args.check)
if not success:
sys.exit(1)
+2 -6
View File
@@ -67,12 +67,8 @@ def _retired_names_used(source: str) -> list[str]:
def _referenced_examples() -> set[str]:
"""Every `docs_src/...` path that some docs page or the README actually includes.
The README is globbed rather than named so this survives the planned
`README.v2.md` -> `README.md` rename instead of crashing on a missing file.
"""
pages = [*sorted((REPO_ROOT / "docs").rglob("*.md")), *sorted(REPO_ROOT.glob("README*.md"))]
"""Every `docs_src/...` path that some docs page or the README actually includes."""
pages = [*sorted((REPO_ROOT / "docs").rglob("*.md")), REPO_ROOT / "README.md"]
return {ref for page in pages for ref in _INCLUDE_DIRECTIVE.findall(page.read_text(encoding="utf-8"))}
+1 -2
View File
@@ -93,7 +93,6 @@ async def test_desktop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
assert "file2.txt" in content.text
# TODO(v2): Change back to README.md when v2 is released.
# `--8<--` include directives lint clean as Python, so pages built from
# `docs_src/` includes cost nothing here; the real validation of those files is
# pyright + ruff + tests/docs_src/.
@@ -101,7 +100,7 @@ async def test_desktop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"example",
list(
find_examples(
"README.v2.md",
"README.md",
"docs/index.md",
"docs/installation.md",
"docs/tutorial",