Promote mcp-types to a core fastmcp-slim dependency

Bare `import fastmcp` loads mcp_types via _sdk_patches and _compat, so a
bare slim install (no [mcp] extra) hit ModuleNotFoundError. mcp-types only
pulls pydantic + typing-extensions (already core), so graduating it to core
fixes the import while the full mcp SDK stays in the [mcp] extra.
This commit is contained in:
Jeremiah Lowin 2026-07-06 10:58:51 -04:00
commit e16ffad420
No known key found for this signature in database
3 changed files with 59 additions and 8 deletions

View file

@ -4,6 +4,7 @@ dynamic = ["version", "optional-dependencies"]
description = "The dependency-slim FastMCP package."
authors = [{ name = "Jeremiah Lowin" }]
dependencies = [
"mcp-types==2.0.0b1",
"platformdirs>=4.0.0",
"pydantic[email]>=2.12.0",
"pydantic-settings>=2.0.0",
@ -79,7 +80,6 @@ mcp = [
"exceptiongroup>=1.2.2",
"httpx>=0.28.1,<1.0",
"mcp==2.0.0b1",
"mcp-types==2.0.0b1",
"opentelemetry-api>=1.28.0",
# starlette floor: transitive via mcp (which only requires >=0.27).
# Pin past CVE-2026-48710, which was patched in 1.0.1.

View file

@ -2,6 +2,9 @@ from __future__ import annotations
import builtins
import contextlib
import subprocess
import sys
import textwrap
import types
from collections.abc import Mapping, Sequence
from typing import Any, cast
@ -59,3 +62,55 @@ async def test_multiserver_config_requires_server_for_now() -> None:
):
async with transport.connect_session():
pass
def test_bare_slim_import_needs_only_mcp_types() -> None:
"""A bare `fastmcp-slim` install ships `mcp-types` but not the full `mcp` SDK.
`mcp-types` is a core dependency (it only pulls pydantic + typing-extensions),
while the full `mcp` package lives in the `[mcp]` extra pulled by
`[client]`/`[server]`. With `mcp` absent but `mcp-types` present, `import
fastmcp`, `import fastmcp.settings`, and `import fastmcp.types` must all
succeed, while `fastmcp.FastMCP` and `fastmcp.Client` raise the friendly
install-hint ImportError (they need `mcp.*` from the server/client extras).
"""
script = textwrap.dedent(
"""
import sys
import importlib.abc
class BlockFullMcp(importlib.abc.MetaPathFinder):
# Block the full `mcp` package but leave `mcp_types` importable,
# exactly as a bare `fastmcp-slim` install would present.
def find_spec(self, name, path, target=None):
if name == "mcp" or name.startswith("mcp."):
raise ImportError(f"blocked: {name}")
return None
sys.meta_path.insert(0, BlockFullMcp())
import fastmcp
import fastmcp.settings
import fastmcp.types
assert "mcp_types" in sys.modules, "mcp_types should load on a bare install"
assert "mcp" not in sys.modules, "full mcp must not load on a bare install"
for attr in ("FastMCP", "Client"):
try:
getattr(fastmcp, attr)
except ImportError:
pass
else:
raise AssertionError(f"fastmcp.{attr} should have raised ImportError")
print("OK")
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
assert result.stdout.strip().endswith("OK")

10
uv.lock generated
View file

@ -10,7 +10,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-06-28T22:01:54.610739Z"
exclude-newer = "2026-06-29T14:57:29.757967Z"
exclude-newer-span = "P1W"
[options.exclude-newer-package]
@ -955,6 +955,7 @@ requires-dist = [{ name = "fastmcp-slim", extras = ["client", "server"], editabl
name = "fastmcp-slim"
source = { editable = "fastmcp_slim" }
dependencies = [
{ name = "mcp-types" },
{ name = "platformdirs" },
{ name = "pydantic", extra = ["email"] },
{ name = "pydantic-settings" },
@ -979,7 +980,6 @@ client = [
{ name = "exceptiongroup" },
{ name = "httpx" },
{ name = "mcp" },
{ name = "mcp-types" },
{ name = "opentelemetry-api" },
{ name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] },
{ name = "starlette" },
@ -995,7 +995,6 @@ mcp = [
{ name = "exceptiongroup" },
{ name = "httpx" },
{ name = "mcp" },
{ name = "mcp-types" },
{ name = "opentelemetry-api" },
{ name = "starlette" },
]
@ -1012,7 +1011,6 @@ server = [
{ name = "jsonref" },
{ name = "jsonschema-path" },
{ name = "mcp" },
{ name = "mcp-types" },
{ name = "openapi-pydantic" },
{ name = "opentelemetry-api" },
{ name = "packaging" },
@ -1052,9 +1050,7 @@ requires-dist = [
{ name = "mcp", marker = "extra == 'client'", specifier = "==2.0.0b1" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = "==2.0.0b1" },
{ name = "mcp", marker = "extra == 'server'", specifier = "==2.0.0b1" },
{ name = "mcp-types", marker = "extra == 'client'", specifier = "==2.0.0b1" },
{ name = "mcp-types", marker = "extra == 'mcp'", specifier = "==2.0.0b1" },
{ name = "mcp-types", marker = "extra == 'server'", specifier = "==2.0.0b1" },
{ name = "mcp-types", specifier = "==2.0.0b1" },
{ name = "openai", marker = "extra == 'openai'", specifier = ">=1.102.0" },
{ name = "openapi-pydantic", marker = "extra == 'server'", specifier = ">=0.5.1" },
{ name = "opentelemetry-api", marker = "extra == 'client'", specifier = ">=1.28.0" },