8900f2527c
A new `mcp-codemod` workspace package (`uvx mcp-codemod v1-to-v2 ./src`) that rewrites every v1 -> v2 change whose meaning is unambiguous from the file alone, and inserts a `# mcp-codemod:` comment above every site it recognized but would not guess at. Built on libCST. Names are resolved through each file's imports, never matched as text, so an aliased import or an unrelated symbol that shares a name with an SDK one is never touched. The camelCase to snake_case rename is restricted to the field names v1's `mcp.types` actually declared. Anything whose correct rewrite depends on information that is not in the file -- the lowlevel decorator to `on_*` relocation, the transport keywords on the `MCPServer` constructor -- is left exactly as written and marked instead, so the remaining work is one grep. Re-running on the output is a no-op. The mapping tables are pinned against the installed v2 package by ratchet tests so they cannot silently drift: every rename target must resolve, every removed API must be provably absent, and no flagged constructor keyword may survive on `MCPServer.__init__`. Measured against the example files that exist on both `v1.x` and `main` (whose diff is the hand-written migration), the codemod fully reproduces 13 of the 51 with a real migration diff, improves 35 more, and makes none worse. Also adds an "Automated migration" section to docs/migration.md, a mention of the tool in README.v2.md, and the package to the publish workflow's build step (the PyPI project and its trusted publisher must exist before a release is tagged with this in it).
311 lines
10 KiB
TOML
311 lines
10 KiB
TOML
[project]
|
|
name = "mcp"
|
|
dynamic = ["version", "dependencies"]
|
|
description = "Model Context Protocol SDK"
|
|
readme = "README.md"
|
|
requires-python = ">=3.10"
|
|
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
|
|
maintainers = [
|
|
{ name = "David Soria Parra", email = "davidsp@anthropic.com" },
|
|
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
|
|
{ name = "Max Isbey", email = "maxisbey@anthropic.com" },
|
|
{ name = "Felix Weinberger", email = "fweinberger@anthropic.com" },
|
|
]
|
|
keywords = ["mcp", "llm", "automation"]
|
|
license = { text = "MIT" }
|
|
classifiers = [
|
|
"Development Status :: 5 - Production/Stable",
|
|
"Intended Audience :: Developers",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Programming Language :: Python :: 3.14",
|
|
]
|
|
[project.optional-dependencies]
|
|
rich = ["rich>=13.9.4"]
|
|
cli = ["typer>=0.16.0", "python-dotenv>=1.0.0"]
|
|
|
|
[project.scripts]
|
|
mcp = "mcp.cli:app [cli]"
|
|
|
|
[tool.uv]
|
|
default-groups = ["dev", "docs"]
|
|
required-version = ">=0.9.5"
|
|
# PEP 517 build isolation fetches [build-system].requires (and transitives) at
|
|
# floating-latest with no hash check on every fresh sync; uv does not lock them
|
|
# (astral-sh/uv#5190). Pinning here narrows that to known-good versions. Covers
|
|
# the workspace builds (hatchling + uv-dynamic-versioning) and the legacy
|
|
# setuptools fallback used by the strict-no-cover git dep.
|
|
build-constraint-dependencies = [
|
|
"hatchling==1.29.0",
|
|
"uv-dynamic-versioning==0.14.0",
|
|
"dunamai==1.26.1",
|
|
"jinja2==3.1.6",
|
|
"markupsafe==3.0.3",
|
|
"packaging==26.1",
|
|
"pathspec==1.0.4",
|
|
"pluggy==1.6.0",
|
|
"tomlkit==0.14.0",
|
|
"trove-classifiers==2026.1.14.14",
|
|
"setuptools==82.0.1",
|
|
]
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
# We add mcp[cli] so `uv sync` considers the extras.
|
|
"mcp[cli]",
|
|
# The codemod is a standalone tool, not a dependency of `mcp`; pull it in here
|
|
# so the workspace's test environment has it.
|
|
"mcp-codemod",
|
|
"mcp-example-stories",
|
|
"tomli>=2.0; python_version < '3.11'",
|
|
"pyright>=1.1.400",
|
|
"pytest>=8.4.0",
|
|
"ruff>=0.8.5",
|
|
"trio>=0.26.2",
|
|
"pytest-flakefinder>=1.1.0",
|
|
"pytest-xdist>=3.6.1",
|
|
"pytest-examples>=0.0.14",
|
|
"pytest-pretty>=1.2.0",
|
|
"inline-snapshot>=0.23.0",
|
|
"dirty-equals>=0.9.0",
|
|
"coverage[toml]>=7.10.7,<=7.13",
|
|
"pillow>=12.0",
|
|
"strict-no-cover",
|
|
"logfire>=3.0.0",
|
|
"opentelemetry-sdk>=1.39.1",
|
|
]
|
|
docs = [
|
|
"mkdocs>=1.6.1",
|
|
"mkdocs-gen-files>=0.5.0",
|
|
"mkdocs-glightbox>=0.4.0",
|
|
"mkdocs-literate-nav>=0.6.1",
|
|
"mkdocs-material[imaging]>=9.5.45",
|
|
"mkdocstrings-python>=2.0.1",
|
|
]
|
|
codegen = ["datamodel-code-generator==0.57.0"]
|
|
|
|
[build-system]
|
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[tool.hatch.version]
|
|
source = "uv-dynamic-versioning"
|
|
|
|
[tool.uv-dynamic-versioning]
|
|
vcs = "git"
|
|
style = "pep440"
|
|
bump = true
|
|
|
|
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
|
|
dependencies = [
|
|
# anyio < 4.10 triggers a compile-time SyntaxWarning on Python 3.14 (PEP 765,
|
|
# "'return' in a 'finally' block"); for stdio servers it lands on the child's
|
|
# stderr (agronholm/anyio#816, fixed in 4.10).
|
|
"anyio>=4.10; python_version >= '3.14'",
|
|
"anyio>=4.9; python_version < '3.14'",
|
|
"httpx>=0.27.1,<1.0.0",
|
|
"httpx-sse>=0.4",
|
|
"mcp-types=={{ version }}",
|
|
"pydantic>=2.12.0",
|
|
"starlette>=0.48.0; python_version >= '3.14'",
|
|
"starlette>=0.27; python_version < '3.14'",
|
|
"python-multipart>=0.0.9",
|
|
"sse-starlette>=3.0.0",
|
|
"pydantic-settings>=2.5.2",
|
|
"uvicorn>=0.31.1; sys_platform != 'emscripten'",
|
|
"jsonschema>=4.20.0",
|
|
"pywin32>=311; sys_platform == 'win32'",
|
|
"pyjwt[crypto]>=2.10.1",
|
|
"typing-extensions>=4.13.0",
|
|
"typing-inspection>=0.4.1",
|
|
"opentelemetry-api>=1.28.0",
|
|
]
|
|
|
|
[project.urls]
|
|
Homepage = "https://modelcontextprotocol.io"
|
|
Documentation = "https://py.sdk.modelcontextprotocol.io/v2/"
|
|
Repository = "https://github.com/modelcontextprotocol/python-sdk"
|
|
Issues = "https://github.com/modelcontextprotocol/python-sdk/issues"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/mcp"]
|
|
|
|
[tool.pyright]
|
|
typeCheckingMode = "strict"
|
|
include = [
|
|
"src/mcp",
|
|
"src/mcp-codemod/mcp_codemod",
|
|
"src/mcp-types/mcp_types",
|
|
"tests",
|
|
"docs_src",
|
|
"examples/stories",
|
|
"examples/servers",
|
|
"examples/snippets",
|
|
"examples/clients",
|
|
]
|
|
venvPath = "."
|
|
venv = ".venv"
|
|
# `stories` is a workspace package rooted at examples/; the IDE language server
|
|
# does not always pick up the editable-install .pth, so resolve it statically.
|
|
extraPaths = ["examples"]
|
|
# The FastAPI style of using decorators in tests gives a `reportUnusedFunction` error.
|
|
# See https://github.com/microsoft/pyright/issues/7771 for more details.
|
|
# TODO(Marcelo): We should remove `reportPrivateUsage = false`. The idea is that we should test the workflow that uses
|
|
# those private functions instead of testing the private functions directly. It makes it easier to maintain the code source
|
|
# and refactor code that is not public.
|
|
executionEnvironments = [
|
|
{ root = "tests", extraPaths = [
|
|
".",
|
|
"examples",
|
|
], reportUnusedFunction = false, reportPrivateUsage = false },
|
|
{ root = "examples/stories", extraPaths = [
|
|
"examples",
|
|
], reportUnusedFunction = false },
|
|
# The `mcp-example-stories` editable install puts `examples/` on sys.path,
|
|
# which defeats pyright's auto-detection of `simple-auth/` as a package
|
|
# root (it's the one server example that imports itself by absolute name).
|
|
{ root = "examples/servers", extraPaths = [
|
|
"examples/servers/simple-auth",
|
|
], reportUnusedFunction = false },
|
|
# docs_src/ holds the complete, runnable code examples included into docs/*.md.
|
|
# Decorated (@mcp.tool/...) module-level functions are never called by name.
|
|
{ root = "docs_src", reportUnusedFunction = false },
|
|
]
|
|
|
|
[tool.ruff]
|
|
line-length = 120
|
|
target-version = "py310"
|
|
|
|
[tool.ruff.lint]
|
|
select = [
|
|
"C4", # flake8-comprehensions
|
|
"C90", # mccabe
|
|
"D212", # pydocstyle: multi-line docstring summary should start at the first line
|
|
"E", # pycodestyle
|
|
"F", # pyflakes
|
|
"I", # isort
|
|
"PERF", # Perflint
|
|
"UP", # pyupgrade
|
|
"TID251", # https://docs.astral.sh/ruff/rules/banned-api/
|
|
]
|
|
ignore = ["PERF203"]
|
|
|
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
"pydantic.RootModel".msg = "Use `pydantic.TypeAdapter` instead."
|
|
|
|
|
|
[tool.ruff.lint.mccabe]
|
|
max-complexity = 24 # Default is 10
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"__init__.py" = ["F401"]
|
|
# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output (TID251 lifts the repo-wide RootModel ban for these generated validators).
|
|
"src/mcp-types/mcp_types/v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"]
|
|
"tests/server/mcpserver/test_func_metadata.py" = ["E501"]
|
|
"tests/shared/test_progress_notifications.py" = ["PLW0603"]
|
|
|
|
[tool.ruff.lint.pylint]
|
|
allow-magic-value-types = ["bytes", "float", "int", "str"]
|
|
max-args = 23 # Default is 5
|
|
max-branches = 23 # Default is 12
|
|
max-returns = 13 # Default is 6
|
|
max-statements = 102 # Default is 50
|
|
|
|
[tool.uv.workspace]
|
|
members = [
|
|
"src/mcp-codemod",
|
|
"src/mcp-types",
|
|
"examples",
|
|
"examples/clients/*",
|
|
"examples/servers/*",
|
|
"examples/snippets",
|
|
]
|
|
|
|
[tool.uv.sources]
|
|
mcp = { workspace = true }
|
|
mcp-codemod = { workspace = true }
|
|
mcp-example-stories = { workspace = true }
|
|
mcp-types = { workspace = true }
|
|
strict-no-cover = { git = "https://github.com/pydantic/strict-no-cover" }
|
|
|
|
[tool.pytest.ini_options]
|
|
log_cli = true
|
|
xfail_strict = true
|
|
markers = [
|
|
"requirement(id): links a test to the entry in tests/interaction/_requirements.py it exercises",
|
|
]
|
|
addopts = """
|
|
--color=yes
|
|
--capture=fd
|
|
-p anyio
|
|
-p examples
|
|
"""
|
|
filterwarnings = [
|
|
"error",
|
|
# pywin32 internal deprecation warning
|
|
"ignore:getargs.*The 'u' format is deprecated:DeprecationWarning",
|
|
# SEP-2577 deprecates the roots/sampling/logging methods; the SDK still calls
|
|
# them internally (e.g. `ctx.debug` -> `log` -> `send_log_message`), so the
|
|
# advisory warning is silenced. Tests asserting it opt back in with pytest.warns.
|
|
"ignore:.*is deprecated as of 2026-07-28 \\(SEP-2577\\).:mcp.MCPDeprecationWarning",
|
|
# 2026-07-28 restricts progress to server->client; the client send path is
|
|
# advisory-deprecated and a handful of tests still exercise it.
|
|
"ignore:Client-to-server progress is deprecated as of 2026-07-28.*:mcp.MCPDeprecationWarning",
|
|
# 2026-07-28 drops ping; Client.send_ping() is advisory-deprecated and the
|
|
# legacy interaction/transport tests still drive it.
|
|
"ignore:ping is removed as of 2026-07-28.*:mcp.MCPDeprecationWarning",
|
|
]
|
|
|
|
[tool.markdown.lint]
|
|
default = true
|
|
MD004 = false # ul-style - Unordered list style
|
|
MD007.indent = 2 # ul-indent - Unordered list indentation
|
|
MD013 = false # line-length - Line length
|
|
MD029 = false # ol-prefix - Ordered list item prefix
|
|
MD033 = false # no-inline-html Inline HTML
|
|
MD041 = false # first-line-heading/first-line-h1
|
|
MD046 = false # indented-code-blocks
|
|
MD059 = false # descriptive-link-text
|
|
|
|
# https://coverage.readthedocs.io/en/latest/config.html#run
|
|
[tool.coverage.run]
|
|
branch = true
|
|
patch = ["subprocess"]
|
|
concurrency = ["multiprocessing", "thread"]
|
|
source = ["src", "src/mcp-codemod/mcp_codemod", "src/mcp-types/mcp_types", "tests"]
|
|
omit = [
|
|
"src/mcp/client/__main__.py",
|
|
"src/mcp/server/__main__.py",
|
|
"src/mcp/os/posix/utilities.py",
|
|
"src/mcp/os/win32/utilities.py",
|
|
]
|
|
|
|
# https://coverage.readthedocs.io/en/latest/config.html#report
|
|
[tool.coverage.report]
|
|
fail_under = 100
|
|
skip_covered = true
|
|
show_missing = true
|
|
ignore_errors = true
|
|
precision = 2
|
|
exclude_also = [
|
|
"pragma: lax no cover",
|
|
"@overload",
|
|
"raise NotImplementedError",
|
|
]
|
|
|
|
# https://coverage.readthedocs.io/en/latest/config.html#paths
|
|
[tool.coverage.paths]
|
|
source = [
|
|
"src/",
|
|
"/home/runner/work/python-sdk/python-sdk/src/",
|
|
'D:\a\python-sdk\python-sdk\src',
|
|
]
|
|
|
|
[tool.inline-snapshot]
|
|
default-flags = ["disable"]
|
|
format-command = "ruff format --stdin-filename {filename}"
|