Promote the v2 README to README.md ahead of the first v2 beta (#3014)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
-132
@@ -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
@@ -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
@@ -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
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))}
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user