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
commit f2e63c979a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 84 additions and 2683 deletions

View file

@ -27,8 +27,6 @@ jobs:
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
with: with:
extra_args: --all-files --verbose extra_args: --all-files --verbose
env:
SKIP: no-commit-to-branch,readme-v1-frozen
- name: Surface types match vendored schema - name: Surface types match vendored schema
run: | run: |
@ -42,19 +40,6 @@ jobs:
uv run --isolated --no-project --with ./src/mcp-types python -c \ 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" "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: test:
name: test (${{ matrix.python-version }}, ${{ matrix.dep-resolution.name }}, ${{ matrix.os }}) name: test (${{ matrix.python-version }}, ${{ matrix.dep-resolution.name }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -116,7 +101,7 @@ jobs:
run: uv sync --frozen --all-extras --python 3.10 run: uv sync --frozen --all-extras --python 3.10
- name: Check README snippets are up to date - 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`, # `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 # but until this job existed the docs were only ever built post-merge by

View file

@ -42,7 +42,6 @@ repos:
types: [python] types: [python]
language: system language: system
pass_filenames: false pass_filenames: false
exclude: ^README(\.v2)?\.md$
- id: pyright - id: pyright
name: pyright name: pyright
entry: uv run --frozen pyright entry: uv run --frozen pyright
@ -55,15 +54,9 @@ repos:
language: system language: system
files: ^(pyproject\.toml|uv\.lock)$ files: ^(pyproject\.toml|uv\.lock)$
pass_filenames: false 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 - id: readme-snippets
name: Check README snippets are up to date name: Check README snippets are up to date
entry: uv run --frozen python scripts/update_readme_snippets.py --check entry: uv run --frozen python scripts/update_readme_snippets.py --check
language: system 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 pass_filenames: false

View file

@ -12,8 +12,7 @@
shim) must be documented in `docs/migration.md`. shim) must be documented in `docs/migration.md`.
- `v1.x` is the release branch for the current stable line. Backport PRs target - `v1.x` is the release branch for the current stable line. Backport PRs target
this branch and use a `[v1.x]` title prefix. this branch and use a `[v1.x]` title prefix.
- `README.md` is frozen at v1 (a pre-commit hook rejects edits). Edit - `README.md` documents v2. The v1 README lives on the `v1.x` branch.
`README.v2.md` instead.
## Package Management ## Package Management

View file

@ -105,7 +105,7 @@ uv run ruff check .
uv run ruff format . 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 ```bash
uv run scripts/update_readme_snippets.py uv run scripts/update_readme_snippets.py

2638
README.md

File diff suppressed because it is too large Load diff

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

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 `Development Status` classifier in both `pyproject.toml` files is permanently
`5 - Production/Stable`; it is not bumped as part of any release. `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 with `continue-on-error`, so a green workflow run does not mean the tests
passed — check the individual jobs. passed — check the individual jobs.
2. Create the release as a pre-release, passing the exact commit verified in 3. 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 step 2 as `--target` (otherwise the tag is created from whatever `main`'s
HEAD is by then). The tagged commit determines everything about the HEAD is by then). The tagged commit determines everything about the
release — the workflows that run and the package metadata (readme, release — the workflows that run and the package metadata (readme,
classifiers) that gets published — so it must contain the current release 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> 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 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 install line (`pip install mcp==2.0.0aN`), and a link to the migration
guide. Use the absolute URL guide. Use the absolute URL
(`https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md`) (`https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md`)
because relative links don't resolve in GitHub release bodies. 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. one. Never delete a release from PyPI — version numbers cannot be reused.
Yanking doesn't stop `==` pins from installing the broken version, so set 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 the yank reason (and edit the GitHub release notes) to point at the

View file

@ -4,9 +4,9 @@
example per protocol feature, each with its own README. Start with example per protocol feature, each with its own README. Start with
[`stories/tools/`](stories/tools/); the [stories README](stories/README.md) [`stories/tools/`](stories/tools/); the [stories README](stories/README.md)
has the full table and how to run them. has the full table and how to run them.
- [`snippets/`](snippets/) — short extracts embedded into `README.v2.md`. Kept - [`snippets/`](snippets/) — short extracts that were embedded into the v1
minimal and in sync with the top-level README; not intended to be run README (now on the `v1.x` branch); superseded by `docs_src/`, which the docs
standalone. and README embed today. Retained pending consolidation into `stories/`.
- [`servers/everything-server/`](servers/everything-server/) — the conformance - [`servers/everything-server/`](servers/everything-server/) — the conformance
target for the cross-SDK target for the cross-SDK
[conformance suite](https://github.com/modelcontextprotocol/conformance). [conformance suite](https://github.com/modelcontextprotocol/conformance).
@ -15,8 +15,8 @@
migration guide; superseded by `stories/` and slated for removal. migration guide; superseded by `stories/` and slated for removal.
- [`clients/`](clients/) and the remaining [`servers/`](servers/) directories - [`clients/`](clients/) and the remaining [`servers/`](servers/) directories
(`simple-*`, `sse-polling-demo`, `structured-output-lowlevel`) — standalone (`simple-*`, `sse-polling-demo`, `structured-output-lowlevel`) — standalone
v1-era projects still linked from `README.v2.md`; retained pending v1-era projects retained pending consolidation into `stories/` (the
consolidation into `stories/`. `simple-auth` pair is still linked from `docs/advanced/`).
For real-world servers see the For real-world servers see the
[servers repository](https://github.com/modelcontextprotocol/servers). [servers repository](https://github.com/modelcontextprotocol/servers).

View file

@ -2,7 +2,7 @@
name = "mcp" name = "mcp"
dynamic = ["version", "dependencies"] dynamic = ["version", "dependencies"]
description = "Model Context Protocol SDK" description = "Model Context Protocol SDK"
readme = "README.v2.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }] authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
maintainers = [ maintainers = [
@ -175,7 +175,6 @@ executionEnvironments = [
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py310" target-version = "py310"
extend-exclude = ["README.md", "README.v2.md"]
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [

View file

@ -93,16 +93,16 @@ def process_snippet_block(match: re.Match[str], check_mode: bool = False) -> str
return full_match 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. """Update code snippets in README.md with live code from source files.
Args: Args:
readme_path: Path to the README file
check_mode: If True, only check if updates are needed without modifying check_mode: If True, only check if updates are needed without modifying
Returns: Returns:
True if file is up to date or was updated, False if check failed True if file is up to date or was updated, False if check failed
""" """
readme_path = Path("README.md")
if not readme_path.exists(): if not readme_path.exists():
print(f"Error: README file not found: {readme_path}") print(f"Error: README file not found: {readme_path}")
return False return False
@ -146,12 +146,10 @@ def main():
parser.add_argument( parser.add_argument(
"--check", action="store_true", help="Check mode - verify snippets are up to date without modifying" "--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() 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: if not success:
sys.exit(1) sys.exit(1)

View file

@ -67,12 +67,8 @@ def _retired_names_used(source: str) -> list[str]:
def _referenced_examples() -> set[str]: def _referenced_examples() -> set[str]:
"""Every `docs_src/...` path that some docs page or the README actually includes. """Every `docs_src/...` path that some docs page or the README actually includes."""
pages = [*sorted((REPO_ROOT / "docs").rglob("*.md")), REPO_ROOT / "README.md"]
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"))]
return {ref for page in pages for ref in _INCLUDE_DIRECTIVE.findall(page.read_text(encoding="utf-8"))} return {ref for page in pages for ref in _INCLUDE_DIRECTIVE.findall(page.read_text(encoding="utf-8"))}

View file

@ -93,7 +93,6 @@ async def test_desktop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
assert "file2.txt" in content.text 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 # `--8<--` include directives lint clean as Python, so pages built from
# `docs_src/` includes cost nothing here; the real validation of those files is # `docs_src/` includes cost nothing here; the real validation of those files is
# pyright + ruff + tests/docs_src/. # pyright + ruff + tests/docs_src/.
@ -101,7 +100,7 @@ async def test_desktop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"example", "example",
list( list(
find_examples( find_examples(
"README.v2.md", "README.md",
"docs/index.md", "docs/index.md",
"docs/installation.md", "docs/installation.md",
"docs/tutorial", "docs/tutorial",