Compare commits

...

4 commits

Author SHA1 Message Date
Jeremiah Lowin
a0a2831401
Merge branch 'main' into codex/lazy-package-exports 2026-08-02 14:11:39 -04:00
Jeremiah Lowin
2906c74b83
Lazy-load public package exports 2026-08-02 09:59:37 -04:00
Jeremiah Lowin
0342e6cccd
Format Prefab import guard 2026-08-02 09:42:14 -04:00
Jeremiah Lowin
5c751510cc
Optimize HTTP server startup imports 2026-08-02 09:41:42 -04:00
11 changed files with 609 additions and 93 deletions

View file

@ -1,27 +1,55 @@
from fastmcp import _install_hints from typing import TYPE_CHECKING
try: from fastmcp import _install_hints
from fastmcp.utilities.lazy_imports import (
list_module_attributes,
resolve_lazy_import,
)
if TYPE_CHECKING:
from .auth import ( from .auth import (
BearerAuth, BearerAuth as BearerAuth,
ClientCredentialsOAuthProvider,
OAuth,
PrivateKeyJWTOAuthProvider,
) )
from .client import Client from .auth import (
ClientCredentialsOAuthProvider as ClientCredentialsOAuthProvider,
)
from .auth import (
OAuth as OAuth,
)
from .auth import (
PrivateKeyJWTOAuthProvider as PrivateKeyJWTOAuthProvider,
)
from .client import Client as Client
from .transports import ( from .transports import (
ClientTransport, ClientTransport as ClientTransport,
FastMCPTransport, )
NodeStdioTransport, from .transports import (
NpxStdioTransport, FastMCPTransport as FastMCPTransport,
PythonStdioTransport, )
SSETransport, from .transports import (
StdioTransport, NodeStdioTransport as NodeStdioTransport,
StreamableHttpTransport, )
UvStdioTransport, from .transports import (
UvxStdioTransport, NpxStdioTransport as NpxStdioTransport,
)
from .transports import (
PythonStdioTransport as PythonStdioTransport,
)
from .transports import (
SSETransport as SSETransport,
)
from .transports import (
StdioTransport as StdioTransport,
)
from .transports import (
StreamableHttpTransport as StreamableHttpTransport,
)
from .transports import (
UvStdioTransport as UvStdioTransport,
)
from .transports import (
UvxStdioTransport as UvxStdioTransport,
) )
except ImportError as exc:
raise ImportError(_install_hints.CLIENT_SUPPORT) from exc
__all__ = [ __all__ = [
"BearerAuth", "BearerAuth",
@ -40,3 +68,35 @@ __all__ = [
"UvStdioTransport", "UvStdioTransport",
"UvxStdioTransport", "UvxStdioTransport",
] ]
_LAZY_IMPORTS = {
"BearerAuth": (".auth", "BearerAuth"),
"Client": (".client", "Client"),
"ClientCredentialsOAuthProvider": (
".auth",
"ClientCredentialsOAuthProvider",
),
"ClientTransport": (".transports", "ClientTransport"),
"FastMCPTransport": (".transports", "FastMCPTransport"),
"NodeStdioTransport": (".transports", "NodeStdioTransport"),
"NpxStdioTransport": (".transports", "NpxStdioTransport"),
"OAuth": (".auth", "OAuth"),
"PrivateKeyJWTOAuthProvider": (".auth", "PrivateKeyJWTOAuthProvider"),
"PythonStdioTransport": (".transports", "PythonStdioTransport"),
"SSETransport": (".transports", "SSETransport"),
"StdioTransport": (".transports", "StdioTransport"),
"StreamableHttpTransport": (".transports", "StreamableHttpTransport"),
"UvStdioTransport": (".transports", "UvStdioTransport"),
"UvxStdioTransport": (".transports", "UvxStdioTransport"),
}
def __getattr__(name: str) -> object:
try:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
except ImportError as exc:
raise ImportError(_install_hints.CLIENT_SUPPORT) from exc
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,11 +1,23 @@
from .bearer import BearerAuth from typing import TYPE_CHECKING
from .client_credentials import (
ClientCredentialsOAuthProvider, from fastmcp.utilities.lazy_imports import (
PrivateKeyJWTOAuthProvider, list_module_attributes,
SignedJWTParameters, resolve_lazy_import,
static_assertion_provider,
) )
from .oauth import OAuth
if TYPE_CHECKING:
from .bearer import BearerAuth as BearerAuth
from .client_credentials import (
ClientCredentialsOAuthProvider as ClientCredentialsOAuthProvider,
)
from .client_credentials import (
PrivateKeyJWTOAuthProvider as PrivateKeyJWTOAuthProvider,
)
from .client_credentials import SignedJWTParameters as SignedJWTParameters
from .client_credentials import (
static_assertion_provider as static_assertion_provider,
)
from .oauth import OAuth as OAuth
__all__ = [ __all__ = [
"BearerAuth", "BearerAuth",
@ -15,3 +27,29 @@ __all__ = [
"SignedJWTParameters", "SignedJWTParameters",
"static_assertion_provider", "static_assertion_provider",
] ]
_LAZY_IMPORTS = {
"BearerAuth": (".bearer", "BearerAuth"),
"ClientCredentialsOAuthProvider": (
".client_credentials",
"ClientCredentialsOAuthProvider",
),
"OAuth": (".oauth", "OAuth"),
"PrivateKeyJWTOAuthProvider": (
".client_credentials",
"PrivateKeyJWTOAuthProvider",
),
"SignedJWTParameters": (".client_credentials", "SignedJWTParameters"),
"static_assertion_provider": (
".client_credentials",
"static_assertion_provider",
),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,25 +1,37 @@
from mcp.server.mcpserver import MCPServer as SDKServer from typing import TYPE_CHECKING
from fastmcp.client.transports.base import ( from fastmcp.utilities.lazy_imports import (
ClientTransport, list_module_attributes,
ClientTransportT, resolve_lazy_import,
SessionKwargs,
)
from fastmcp.client.transports.config import MCPConfigTransport
from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.inference import infer_transport
from fastmcp.client.transports.sse import SSETransport
from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.client.transports.stdio import (
FastMCPStdioTransport,
NodeStdioTransport,
NpxStdioTransport,
PythonStdioTransport,
StdioTransport,
UvStdioTransport,
UvxStdioTransport,
) )
if TYPE_CHECKING:
from mcp.server.mcpserver import MCPServer as SDKServer
from fastmcp.client.transports.base import ClientTransport as ClientTransport
from fastmcp.client.transports.base import ClientTransportT as ClientTransportT
from fastmcp.client.transports.base import SessionKwargs as SessionKwargs
from fastmcp.client.transports.config import (
MCPConfigTransport as MCPConfigTransport,
)
from fastmcp.client.transports.http import (
StreamableHttpTransport as StreamableHttpTransport,
)
from fastmcp.client.transports.inference import infer_transport as infer_transport
from fastmcp.client.transports.memory import FastMCPTransport as FastMCPTransport
from fastmcp.client.transports.sse import SSETransport as SSETransport
from fastmcp.client.transports.stdio import (
FastMCPStdioTransport as FastMCPStdioTransport,
)
from fastmcp.client.transports.stdio import NodeStdioTransport as NodeStdioTransport
from fastmcp.client.transports.stdio import NpxStdioTransport as NpxStdioTransport
from fastmcp.client.transports.stdio import (
PythonStdioTransport as PythonStdioTransport,
)
from fastmcp.client.transports.stdio import StdioTransport as StdioTransport
from fastmcp.client.transports.stdio import UvStdioTransport as UvStdioTransport
from fastmcp.client.transports.stdio import UvxStdioTransport as UvxStdioTransport
__all__ = [ __all__ = [
"ClientTransport", "ClientTransport",
"FastMCPStdioTransport", "FastMCPStdioTransport",
@ -34,3 +46,48 @@ __all__ = [
"UvxStdioTransport", "UvxStdioTransport",
"infer_transport", "infer_transport",
] ]
_LAZY_IMPORTS = {
"ClientTransport": ("fastmcp.client.transports.base", "ClientTransport"),
"ClientTransportT": ("fastmcp.client.transports.base", "ClientTransportT"),
"FastMCPStdioTransport": (
"fastmcp.client.transports.stdio",
"FastMCPStdioTransport",
),
"FastMCPTransport": ("fastmcp.client.transports.memory", "FastMCPTransport"),
"MCPConfigTransport": (
"fastmcp.client.transports.config",
"MCPConfigTransport",
),
"NodeStdioTransport": (
"fastmcp.client.transports.stdio",
"NodeStdioTransport",
),
"NpxStdioTransport": (
"fastmcp.client.transports.stdio",
"NpxStdioTransport",
),
"PythonStdioTransport": (
"fastmcp.client.transports.stdio",
"PythonStdioTransport",
),
"SDKServer": ("mcp.server.mcpserver", "MCPServer"),
"SSETransport": ("fastmcp.client.transports.sse", "SSETransport"),
"SessionKwargs": ("fastmcp.client.transports.base", "SessionKwargs"),
"StdioTransport": ("fastmcp.client.transports.stdio", "StdioTransport"),
"StreamableHttpTransport": (
"fastmcp.client.transports.http",
"StreamableHttpTransport",
),
"UvStdioTransport": ("fastmcp.client.transports.stdio", "UvStdioTransport"),
"UvxStdioTransport": ("fastmcp.client.transports.stdio", "UvxStdioTransport"),
"infer_transport": ("fastmcp.client.transports.inference", "infer_transport"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,5 +1,18 @@
from .function_prompt import FunctionPrompt, prompt from typing import TYPE_CHECKING
from .base import Message, Prompt, PromptArgument, PromptMessage, PromptResult
from fastmcp.utilities.lazy_imports import (
list_module_attributes,
resolve_lazy_import,
)
if TYPE_CHECKING:
from .base import Message as Message
from .base import Prompt as Prompt
from .base import PromptArgument as PromptArgument
from .base import PromptMessage as PromptMessage
from .base import PromptResult as PromptResult
from .function_prompt import FunctionPrompt as FunctionPrompt
from .function_prompt import prompt as prompt
__all__ = [ __all__ = [
"FunctionPrompt", "FunctionPrompt",
@ -10,3 +23,21 @@ __all__ = [
"PromptResult", "PromptResult",
"prompt", "prompt",
] ]
_LAZY_IMPORTS = {
"FunctionPrompt": (".function_prompt", "FunctionPrompt"),
"Message": (".base", "Message"),
"Prompt": (".base", "Prompt"),
"PromptArgument": (".base", "PromptArgument"),
"PromptMessage": (".base", "PromptMessage"),
"PromptResult": (".base", "PromptResult"),
"prompt": (".function_prompt", "prompt"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,15 +1,24 @@
from .function_resource import FunctionResource, resource from typing import TYPE_CHECKING
from .base import Resource, ResourceContent, ResourceResult
from .security import ResourceSecurity from fastmcp.utilities.lazy_imports import (
from .template import ResourceTemplate list_module_attributes,
from .types import ( resolve_lazy_import,
BinaryResource,
DirectoryResource,
FileResource,
HttpResource,
TextResource,
) )
if TYPE_CHECKING:
from .base import Resource as Resource
from .base import ResourceContent as ResourceContent
from .base import ResourceResult as ResourceResult
from .function_resource import FunctionResource as FunctionResource
from .function_resource import resource as resource
from .security import ResourceSecurity as ResourceSecurity
from .template import ResourceTemplate as ResourceTemplate
from .types import BinaryResource as BinaryResource
from .types import DirectoryResource as DirectoryResource
from .types import FileResource as FileResource
from .types import HttpResource as HttpResource
from .types import TextResource as TextResource
__all__ = [ __all__ = [
"BinaryResource", "BinaryResource",
"DirectoryResource", "DirectoryResource",
@ -24,3 +33,26 @@ __all__ = [
"TextResource", "TextResource",
"resource", "resource",
] ]
_LAZY_IMPORTS = {
"BinaryResource": (".types", "BinaryResource"),
"DirectoryResource": (".types", "DirectoryResource"),
"FileResource": (".types", "FileResource"),
"FunctionResource": (".function_resource", "FunctionResource"),
"HttpResource": (".types", "HttpResource"),
"Resource": (".base", "Resource"),
"ResourceContent": (".base", "ResourceContent"),
"ResourceResult": (".base", "ResourceResult"),
"ResourceSecurity": (".security", "ResourceSecurity"),
"ResourceTemplate": (".template", "ResourceTemplate"),
"TextResource": (".types", "TextResource"),
"resource": (".function_resource", "resource"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,10 +1,16 @@
from .authorization import AuthMiddleware from typing import TYPE_CHECKING
from .middleware import (
CallNext, from fastmcp.utilities.lazy_imports import (
Middleware, list_module_attributes,
MiddlewareContext, resolve_lazy_import,
) )
from .ping import PingMiddleware
if TYPE_CHECKING:
from .authorization import AuthMiddleware as AuthMiddleware
from .middleware import CallNext as CallNext
from .middleware import Middleware as Middleware
from .middleware import MiddlewareContext as MiddlewareContext
from .ping import PingMiddleware as PingMiddleware
__all__ = [ __all__ = [
"AuthMiddleware", "AuthMiddleware",
@ -13,3 +19,19 @@ __all__ = [
"MiddlewareContext", "MiddlewareContext",
"PingMiddleware", "PingMiddleware",
] ]
_LAZY_IMPORTS = {
"AuthMiddleware": (".authorization", "AuthMiddleware"),
"CallNext": (".middleware", "CallNext"),
"Middleware": (".middleware", "Middleware"),
"MiddlewareContext": (".middleware", "MiddlewareContext"),
"PingMiddleware": (".ping", "PingMiddleware"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -27,20 +27,32 @@ Example:
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from fastmcp.server.providers.aggregate import AggregateProvider from fastmcp.utilities.lazy_imports import (
from fastmcp.server.providers.base import Provider list_module_attributes,
from fastmcp.server.providers.fastmcp_provider import FastMCPProvider resolve_lazy_import,
from fastmcp.server.providers.filesystem import FileSystemProvider
from fastmcp.server.providers.local_provider import LocalProvider
from fastmcp.server.providers.skills import (
ClaudeSkillsProvider,
SkillProvider,
SkillsDirectoryProvider,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from fastmcp.server.providers.aggregate import (
AggregateProvider as AggregateProvider,
)
from fastmcp.server.providers.base import Provider as Provider
from fastmcp.server.providers.fastmcp_provider import (
FastMCPProvider as FastMCPProvider,
)
from fastmcp.server.providers.filesystem import (
FileSystemProvider as FileSystemProvider,
)
from fastmcp.server.providers.local_provider import LocalProvider as LocalProvider
from fastmcp.server.providers.openapi import OpenAPIProvider as OpenAPIProvider from fastmcp.server.providers.openapi import OpenAPIProvider as OpenAPIProvider
from fastmcp.server.providers.proxy import ProxyProvider as ProxyProvider from fastmcp.server.providers.proxy import ProxyProvider as ProxyProvider
from fastmcp.server.providers.skills import (
ClaudeSkillsProvider as ClaudeSkillsProvider,
)
from fastmcp.server.providers.skills import SkillProvider as SkillProvider
from fastmcp.server.providers.skills import (
SkillsDirectoryProvider as SkillsDirectoryProvider,
)
__all__ = [ __all__ = [
"AggregateProvider", "AggregateProvider",
@ -55,15 +67,35 @@ __all__ = [
"SkillsDirectoryProvider", "SkillsDirectoryProvider",
] ]
_LAZY_IMPORTS = {
"AggregateProvider": ("fastmcp.server.providers.aggregate", "AggregateProvider"),
"ClaudeSkillsProvider": (
"fastmcp.server.providers.skills",
"ClaudeSkillsProvider",
),
"FastMCPProvider": (
"fastmcp.server.providers.fastmcp_provider",
"FastMCPProvider",
),
"FileSystemProvider": (
"fastmcp.server.providers.filesystem",
"FileSystemProvider",
),
"LocalProvider": ("fastmcp.server.providers.local_provider", "LocalProvider"),
"OpenAPIProvider": ("fastmcp.server.providers.openapi", "OpenAPIProvider"),
"Provider": ("fastmcp.server.providers.base", "Provider"),
"ProxyProvider": ("fastmcp.server.providers.proxy", "ProxyProvider"),
"SkillProvider": ("fastmcp.server.providers.skills", "SkillProvider"),
"SkillsDirectoryProvider": (
"fastmcp.server.providers.skills",
"SkillsDirectoryProvider",
),
}
def __getattr__(name: str) -> object: def __getattr__(name: str) -> object:
"""Lazy import for providers to avoid circular imports.""" return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
if name == "ProxyProvider":
from fastmcp.server.providers.proxy import ProxyProvider
return ProxyProvider
if name == "OpenAPIProvider":
from fastmcp.server.providers.openapi import OpenAPIProvider
return OpenAPIProvider def __dir__() -> list[str]:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -23,12 +23,31 @@ from __future__ import annotations
from collections.abc import Awaitable, Sequence from collections.abc import Awaitable, Sequence
from typing import TYPE_CHECKING, Protocol from typing import TYPE_CHECKING, Protocol
from fastmcp.utilities.lazy_imports import (
list_module_attributes,
resolve_lazy_import,
)
from fastmcp.utilities.versions import VersionSpec from fastmcp.utilities.versions import VersionSpec
if TYPE_CHECKING: if TYPE_CHECKING:
from fastmcp.prompts.base import Prompt from fastmcp.prompts.base import Prompt
from fastmcp.resources.base import Resource from fastmcp.resources.base import Resource
from fastmcp.resources.template import ResourceTemplate from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.transforms.namespace import Namespace as Namespace
from fastmcp.server.transforms.prompts_as_tools import (
PromptsAsTools as PromptsAsTools,
)
from fastmcp.server.transforms.resources_as_tools import (
ResourcesAsTools as ResourcesAsTools,
)
from fastmcp.server.transforms.tool_transform import (
ToolTransform as ToolTransform,
)
from fastmcp.server.transforms.version_filter import (
VersionFilter as VersionFilter,
)
from fastmcp.server.transforms.visibility import Visibility as Visibility
from fastmcp.server.transforms.visibility import is_enabled as is_enabled
from fastmcp.tools.base import Tool from fastmcp.tools.base import Tool
@ -219,14 +238,6 @@ class Transform:
return await call_next(name, version=version) return await call_next(name, version=version)
# Re-export built-in transforms (must be after Transform class to avoid circular imports)
from fastmcp.server.transforms.visibility import Visibility, is_enabled # noqa: E402
from fastmcp.server.transforms.namespace import Namespace # noqa: E402
from fastmcp.server.transforms.prompts_as_tools import PromptsAsTools # noqa: E402
from fastmcp.server.transforms.resources_as_tools import ResourcesAsTools # noqa: E402
from fastmcp.server.transforms.tool_transform import ToolTransform # noqa: E402
from fastmcp.server.transforms.version_filter import VersionFilter # noqa: E402
__all__ = [ __all__ = [
"Namespace", "Namespace",
"PromptsAsTools", "PromptsAsTools",
@ -238,3 +249,33 @@ __all__ = [
"Visibility", "Visibility",
"is_enabled", "is_enabled",
] ]
_LAZY_IMPORTS = {
"Namespace": ("fastmcp.server.transforms.namespace", "Namespace"),
"PromptsAsTools": (
"fastmcp.server.transforms.prompts_as_tools",
"PromptsAsTools",
),
"ResourcesAsTools": (
"fastmcp.server.transforms.resources_as_tools",
"ResourcesAsTools",
),
"ToolTransform": (
"fastmcp.server.transforms.tool_transform",
"ToolTransform",
),
"VersionFilter": (
"fastmcp.server.transforms.version_filter",
"VersionFilter",
),
"Visibility": ("fastmcp.server.transforms.visibility", "Visibility"),
"is_enabled": ("fastmcp.server.transforms.visibility", "is_enabled"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -1,6 +1,18 @@
from .function_tool import FunctionTool, tool from typing import TYPE_CHECKING
from .base import InputRequiredToolResult, Tool, ToolResult
from .tool_transform import forward, forward_raw from fastmcp.utilities.lazy_imports import (
list_module_attributes,
resolve_lazy_import,
)
if TYPE_CHECKING:
from .base import InputRequiredToolResult as InputRequiredToolResult
from .base import Tool as Tool
from .base import ToolResult as ToolResult
from .function_tool import FunctionTool as FunctionTool
from .function_tool import tool as tool
from .tool_transform import forward as forward
from .tool_transform import forward_raw as forward_raw
__all__ = [ __all__ = [
"FunctionTool", "FunctionTool",
@ -11,3 +23,21 @@ __all__ = [
"forward_raw", "forward_raw",
"tool", "tool",
] ]
_LAZY_IMPORTS = {
"FunctionTool": (".function_tool", "FunctionTool"),
"InputRequiredToolResult": (".base", "InputRequiredToolResult"),
"Tool": (".base", "Tool"),
"ToolResult": (".base", "ToolResult"),
"forward": (".tool_transform", "forward"),
"forward_raw": (".tool_transform", "forward_raw"),
"tool": (".function_tool", "tool"),
}
def __getattr__(name: str) -> object:
return resolve_lazy_import(name, __name__, globals(), _LAZY_IMPORTS)
def __dir__() -> list[str]:
return list_module_attributes(globals(), _LAZY_IMPORTS)

View file

@ -0,0 +1,31 @@
"""Helpers for exposing public names without eagerly importing their modules."""
from collections.abc import Mapping
from importlib import import_module
from typing import Any
LazyImports = Mapping[str, tuple[str, str]]
def resolve_lazy_import(
name: str,
package: str,
namespace: dict[str, Any],
lazy_imports: LazyImports,
) -> object:
"""Resolve and cache a lazily exported module attribute."""
try:
module_name, attr_name = lazy_imports[name]
except KeyError:
raise AttributeError(f"module {package!r} has no attribute {name!r}") from None
value = getattr(import_module(module_name, package), attr_name)
namespace[name] = value
return value
def list_module_attributes(
namespace: dict[str, Any], lazy_imports: LazyImports
) -> list[str]:
"""Include unresolved lazy exports in module introspection."""
return sorted(namespace.keys() | lazy_imports.keys())

View file

@ -0,0 +1,142 @@
"""Fresh-interpreter guards for lazy public package exports."""
from __future__ import annotations
import subprocess
import sys
import textwrap
import pytest
@pytest.mark.parametrize(
("statement", "excluded_modules"),
[
(
"from fastmcp.client import BearerAuth",
(
"fastmcp.client.auth.client_credentials",
"fastmcp.client.auth.oauth",
"fastmcp.client.client",
"fastmcp.client.transports",
),
),
(
"from fastmcp.client.transports import ClientTransport",
(
"fastmcp.client.transports.config",
"fastmcp.client.transports.http",
"fastmcp.client.transports.inference",
"fastmcp.client.transports.memory",
"fastmcp.client.transports.sse",
"fastmcp.client.transports.stdio",
),
),
(
"from fastmcp.tools import Tool",
("fastmcp.tools.function_tool", "fastmcp.tools.tool_transform"),
),
(
"from fastmcp.resources import Resource",
(
"fastmcp.resources.function_resource",
"fastmcp.resources.security",
"fastmcp.resources.template",
"fastmcp.resources.types",
),
),
(
"from fastmcp.prompts import Prompt",
("fastmcp.prompts.function_prompt",),
),
(
"from fastmcp.server.providers import Provider",
(
"fastmcp.server.providers.aggregate",
"fastmcp.server.providers.fastmcp_provider",
"fastmcp.server.providers.filesystem",
"fastmcp.server.providers.local_provider",
"fastmcp.server.providers.skills",
),
),
(
"from fastmcp.server.middleware import Middleware",
(
"fastmcp.server.middleware.authorization",
"fastmcp.server.middleware.ping",
),
),
(
"from fastmcp.server.transforms import Transform",
(
"fastmcp.server.transforms.namespace",
"fastmcp.server.transforms.prompts_as_tools",
"fastmcp.server.transforms.resources_as_tools",
"fastmcp.server.transforms.tool_transform",
"fastmcp.server.transforms.version_filter",
"fastmcp.server.transforms.visibility",
),
),
],
)
@pytest.mark.subprocess_heavy
def test_narrow_import_does_not_load_sibling_implementations(
statement: str, excluded_modules: tuple[str, ...]
) -> None:
script = textwrap.dedent(
f"""
import sys
{statement}
excluded = {excluded_modules!r}
loaded = [
name
for name in sys.modules
if any(name == root or name.startswith(f"{{root}}.") for root in excluded)
]
assert not loaded, loaded
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
@pytest.mark.subprocess_heavy
def test_all_public_package_exports_resolve() -> None:
script = textwrap.dedent(
"""
import importlib
packages = (
"fastmcp.client",
"fastmcp.client.auth",
"fastmcp.client.transports",
"fastmcp.prompts",
"fastmcp.resources",
"fastmcp.server.middleware",
"fastmcp.server.providers",
"fastmcp.server.transforms",
"fastmcp.tools",
)
for package_name in packages:
package = importlib.import_module(package_name)
assert set(package.__all__) <= set(dir(package))
for export in package.__all__:
assert getattr(package, export) is not None, (package_name, export)
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr