mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
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:
parent
13c5978779
commit
e16ffad420
3 changed files with 59 additions and 8 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
10
uv.lock
generated
|
|
@ -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" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue