ec5b2258c9
Every import path now pays only for what it uses, with no public API added or removed: - The protocol models (mcp.types / mcp_types, incl. the JSON-RPC envelopes and the generated per-version wire packages) build their pydantic validators on first use instead of at import (defer_build), through one shared private base class. First-use builds are serialised behind a single process-wide lock, since released pydantic does not make concurrent first use of a deferred model thread-safe; this also fixes a pre-existing concurrent-first-use failure that reproduces on main. - `import mcp` binds the client/server names lazily on first attribute access (PEP 562) instead of importing both stacks eagerly, and the client no longer imports the server, so client entry points stop loading the server, the web stack, httpx2 and cryptography. - The web application stack (starlette's app machinery, sse_starlette, uvicorn) loads with the app builders that use it, and each protocol version's wire package loads on the first message parsed for that version rather than both loading at import. On the fresh-interpreter harness `import mcp` is ~0.4x of v1 (main is ~1.6x), the client entry points ~0.6x of v1, `import mcp.server.mcpserver` ~0.7x, and time-to-ready / stdio cold start land at parity with v1. RSS after `import mcp` is 19 MiB (v1 43.5, main 57). Steady-state per-call latency is unchanged. Observable-but-incidental differences (removed incidental namespace bindings, deeper submodules no longer imported as a side effect of a bare `import mcp`, get_type_hints needing localns= for a documented set of callables, pre-first-use introspection) are catalogued in docs/migration.md; ratchet tests pin the import footprints and the concurrent-first-use safety.
321 lines
12 KiB
TOML
321 lines
12 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]",
|
|
"mcp-example-stories",
|
|
# pydantic-settings is used only by examples (simple-auth, mcpserver/text_me);
|
|
# keep it reachable for pyright, which covers examples/servers.
|
|
"pydantic-settings>=2.5.2",
|
|
"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 = [
|
|
# Zensical is the Material team's successor to MkDocs; it natively
|
|
# re-implements search, glightbox and mkdocstrings but runs no arbitrary
|
|
# MkDocs plugins or hooks, so the API reference (formerly gen-files +
|
|
# literate-nav) and llms.txt (formerly a hook) are generated by the
|
|
# standalone scripts under scripts/docs/. See scripts/docs/build.sh.
|
|
# 0.0.48 fixed relative/scoped cross-references for mkdocstrings-python
|
|
# (which the mkdocstrings config in mkdocs.yml relies on) but broke
|
|
# search; 0.0.50 fixes it. The toolchain is pinned exactly: Zensical is
|
|
# pre-1.0 and the build guards key on its rendering behavior, so bumps
|
|
# should be deliberate.
|
|
"zensical==0.0.50",
|
|
# Zensical's mkdocstrings compatibility layer targets the mkdocstrings 1.x /
|
|
# mkdocstrings-python 2.0.5+ API (griffe 2 / griffelib); the older
|
|
# mkdocstrings 0.30 / python 2.0.1 line renders API pages with an
|
|
# unregistered-autorefs KeyError under Zensical.
|
|
"mkdocstrings==1.0.4",
|
|
"mkdocstrings-python==2.0.5",
|
|
# scripts/docs/build_config.py and llms_txt.py read mkdocs.yml directly.
|
|
"pyyaml>=6.0.2",
|
|
# gen_ref_pages.py imports griffe directly. griffelib is not a typo: it is
|
|
# griffe's successor distribution (same author) and still imports as
|
|
# `griffe`; the old `griffe` distribution is the incompatible 1.x line.
|
|
"griffelib==2.1.0",
|
|
]
|
|
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'",
|
|
"httpx2>=2.5.0",
|
|
"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",
|
|
"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/"
|
|
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-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"]
|
|
# The mcp.types package is an alias that mirrors mcp_types namespaces by design.
|
|
"src/mcp/types/*.py" = ["F403"]
|
|
# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output (TID251 lifts the repo-wide RootModel ban for these generated validators
|
|
# and their shared `WireRootModel` base).
|
|
"src/mcp-types/mcp_types/_v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"]
|
|
"src/mcp-types/mcp_types/_wire_base.py" = ["TID251"]
|
|
"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-types", "examples", "examples/clients/*", "examples/servers/*", "examples/snippets"]
|
|
|
|
[tool.uv.sources]
|
|
mcp = { 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-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}"
|