From fbf84615dfb1879d3ac09ea320544e5b2d962fb2 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:12:32 +0100 Subject: [PATCH 1/3] refactor: move inline imports to module level Move inline imports to the top of files for consistency. ## src/ changes - server/fastmcp/server.py: Move auth routes and Middleware imports - server/lowlevel/server.py: Move importlib.metadata.version import - server/__main__.py: Move warnings import - client/__main__.py: Move warnings import - client/auth/extensions/client_credentials.py: Move warnings import ## tests/ changes - test_examples.py: Move example imports, rename to avoid conflicts - test_error_handling.py: Move base64, hashlib, secrets, settings imports - test_protected_resource.py: Move urlparse import - test_func_metadata.py: Move InvalidSignature and NamedTuple imports - test_server.py: Move Starlette and ToolError imports - test_integration.py: Move type imports - test_url_elicitation.py: Move elicit_url and pydantic imports - test_auth_integration.py: Remove duplicate base64 imports - test_streamable_http.py: Move traceback import - test_ws.py: Move urlparse import - test_auth_utils.py: Move HttpUrl import - test_sampling_callback.py: Move FastMCP import - test_list_roots_callback.py: Move FastMCP import - test_logging_callback.py: Move FastMCP import - test_auth.py: Consolidate urllib.parse and add auth imports - test_stdio.py: Move _terminate_process_tree import - test_http_unicode.py: Move server-related imports (keep uvicorn inline) - test_session_group.py: Move httpx import - test_output_schema_validation.py: Move jsonschema and inspect imports - test_client_credentials.py: Move warnings import ## Intentionally kept as inline imports - Optional deps (uvicorn, rich, typer, dotenv, jsonschema in src/) - Platform-specific (win32) - TYPE_CHECKING blocks - Circular import avoidance (context_injection.py) Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 0 Claude-Permission-Prompts: 2 Claude-Escapes: 0 --- src/mcp/client/__main__.py | 3 +-- .../auth/extensions/client_credentials.py | 3 +-- src/mcp/server/__main__.py | 3 +-- src/mcp/server/fastmcp/server.py | 18 ++++--------- src/mcp/server/lowlevel/server.py | 5 ++-- .../extensions/test_client_credentials.py | 3 +-- tests/client/test_auth.py | 11 +++----- tests/client/test_http_unicode.py | 23 +++++++--------- tests/client/test_list_roots_callback.py | 3 +-- tests/client/test_logging_callback.py | 3 +-- tests/client/test_output_schema_validation.py | 6 ++--- tests/client/test_sampling_callback.py | 5 +--- tests/client/test_session_group.py | 3 +-- tests/client/test_stdio.py | 13 +++++----- tests/server/auth/test_error_handling.py | 10 +++---- tests/server/auth/test_protected_resource.py | 6 ++--- .../fastmcp/auth/test_auth_integration.py | 4 --- tests/server/fastmcp/test_func_metadata.py | 14 ++-------- tests/server/fastmcp/test_integration.py | 8 ++---- tests/server/fastmcp/test_server.py | 6 ++--- tests/server/fastmcp/test_url_elicitation.py | 11 ++------ tests/shared/test_auth_utils.py | 4 +-- tests/shared/test_streamable_http.py | 5 +--- tests/shared/test_ws.py | 3 +-- tests/test_examples.py | 26 +++++++------------ 25 files changed, 64 insertions(+), 135 deletions(-) diff --git a/src/mcp/client/__main__.py b/src/mcp/client/__main__.py index 2efe05d536..bef466b30e 100644 --- a/src/mcp/client/__main__.py +++ b/src/mcp/client/__main__.py @@ -1,6 +1,7 @@ import argparse import logging import sys +import warnings from functools import partial from urllib.parse import urlparse @@ -15,8 +16,6 @@ from mcp.shared.session import RequestResponder if not sys.warnoptions: - import warnings - warnings.simplefilter("ignore") logging.basicConfig(level=logging.INFO) diff --git a/src/mcp/client/auth/extensions/client_credentials.py b/src/mcp/client/auth/extensions/client_credentials.py index e2f3f08a4d..510cdb2d61 100644 --- a/src/mcp/client/auth/extensions/client_credentials.py +++ b/src/mcp/client/auth/extensions/client_credentials.py @@ -9,6 +9,7 @@ """ import time +import warnings from collections.abc import Awaitable, Callable from typing import Any, Literal from uuid import uuid4 @@ -409,8 +410,6 @@ def __init__( timeout: float = 300.0, jwt_parameters: JWTParameters | None = None, ) -> None: - import warnings - warnings.warn( "RFC7523OAuthClientProvider is deprecated. Use ClientCredentialsOAuthProvider " "or PrivateKeyJWTOAuthProvider instead.", diff --git a/src/mcp/server/__main__.py b/src/mcp/server/__main__.py index 1970eca7d3..dbc50b8a79 100644 --- a/src/mcp/server/__main__.py +++ b/src/mcp/server/__main__.py @@ -1,6 +1,7 @@ import importlib.metadata import logging import sys +import warnings import anyio @@ -10,8 +11,6 @@ from mcp.types import ServerCapabilities if not sys.warnoptions: - import warnings - warnings.simplefilter("ignore") logging.basicConfig(level=logging.INFO) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 0d18df1131..5e50e892fc 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -24,6 +24,11 @@ from mcp.server.auth.middleware.auth_context import AuthContextMiddleware from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier +from mcp.server.auth.routes import ( + build_resource_metadata_url, + create_auth_routes, + create_protected_resource_routes, +) from mcp.server.auth.settings import AuthSettings from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, UrlElicitationResult, elicit_with_validation from mcp.server.elicitation import elicit_url as _elicit_url @@ -810,8 +815,6 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no # Add auth endpoints if auth server provider is configured if self._auth_server_provider: - from mcp.server.auth.routes import create_auth_routes - routes.extend( create_auth_routes( provider=self._auth_server_provider, @@ -827,8 +830,6 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no # Determine resource metadata URL resource_metadata_url = None if self.settings.auth and self.settings.auth.resource_server_url: - from mcp.server.auth.routes import build_resource_metadata_url - # Build compliant metadata URL for WWW-Authenticate header resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) @@ -868,8 +869,6 @@ async def sse_endpoint(request: Request) -> Response: ) # Add protected resource metadata endpoint if configured as RS if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover - from mcp.server.auth.routes import create_protected_resource_routes - routes.extend( create_protected_resource_routes( resource_url=self.settings.auth.resource_server_url, @@ -886,7 +885,6 @@ async def sse_endpoint(request: Request) -> Response: def streamable_http_app(self) -> Starlette: """Return an instance of the StreamableHTTP server app.""" - from starlette.middleware import Middleware # Create session manager on first call (lazy initialization) if self._session_manager is None: # pragma: no branch @@ -923,8 +921,6 @@ def streamable_http_app(self) -> Starlette: # Add auth endpoints if auth server provider is configured if self._auth_server_provider: - from mcp.server.auth.routes import create_auth_routes - routes.extend( create_auth_routes( provider=self._auth_server_provider, @@ -940,8 +936,6 @@ def streamable_http_app(self) -> Starlette: # Determine resource metadata URL resource_metadata_url = None if self.settings.auth and self.settings.auth.resource_server_url: - from mcp.server.auth.routes import build_resource_metadata_url - # Build compliant metadata URL for WWW-Authenticate header resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) @@ -962,8 +956,6 @@ def streamable_http_app(self) -> Starlette: # Add protected resource metadata endpoint if configured as RS if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover - from mcp.server.auth.routes import create_protected_resource_routes - routes.extend( create_protected_resource_routes( resource_url=self.settings.auth.resource_server_url, diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 491ff7d0b3..34f156dfe6 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -74,6 +74,7 @@ async def main(): import warnings from collections.abc import AsyncIterator, Awaitable, Callable, Iterable from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager +from importlib.metadata import version as importlib_version from typing import Any, Generic, TypeAlias, cast import anyio @@ -173,9 +174,7 @@ def create_initialization_options( def pkg_version(package: str) -> str: try: - from importlib.metadata import version - - return version(package) + return importlib_version(package) except Exception: # pragma: no cover pass diff --git a/tests/client/auth/extensions/test_client_credentials.py b/tests/client/auth/extensions/test_client_credentials.py index 6d134af742..a4faada4a8 100644 --- a/tests/client/auth/extensions/test_client_credentials.py +++ b/tests/client/auth/extensions/test_client_credentials.py @@ -1,4 +1,5 @@ import urllib.parse +import warnings import jwt import pytest @@ -60,8 +61,6 @@ async def callback_handler() -> tuple[str, str | None]: # pragma: no cover """Mock callback handler.""" return "test_auth_code", "test_state" - import warnings - with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) return RFC7523OAuthClientProvider( diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 6025ff811b..6df63b0bfc 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -5,7 +5,7 @@ import base64 import time from unittest import mock -from urllib.parse import unquote +from urllib.parse import parse_qs, quote, unquote, urlparse import httpx import pytest @@ -27,6 +27,8 @@ is_valid_client_metadata_url, should_use_client_metadata_url, ) +from mcp.server.auth.routes import build_metadata +from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions from mcp.shared.auth import ( OAuthClientInformationFull, OAuthClientMetadata, @@ -758,8 +760,6 @@ async def test_resource_param_included_with_recent_protocol_version(self, oauth_ content = request.content.decode() assert "resource=" in content # Check URL-encoded resource parameter - from urllib.parse import quote - expected_resource = quote(oauth_provider.context.get_resource_url(), safe="") assert f"resource={expected_resource}" in content @@ -1226,8 +1226,6 @@ async def capture_redirect(url: str) -> None: "%3A", ":" ).replace("+", " ") # Extract state from redirect URL - from urllib.parse import parse_qs, urlparse - parsed = urlparse(url) params = parse_qs(parsed.query) captured_state = params.get("state", [None])[0] @@ -1336,9 +1334,6 @@ def test_build_metadata( registration_endpoint: str, revocation_endpoint: str, ): - from mcp.server.auth.routes import build_metadata - from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions - metadata = build_metadata( issuer_url=AnyHttpUrl(issuer_url), service_documentation_url=AnyHttpUrl(service_documentation_url), diff --git a/tests/client/test_http_unicode.py b/tests/client/test_http_unicode.py index ec38f35838..774b832caa 100644 --- a/tests/client/test_http_unicode.py +++ b/tests/client/test_http_unicode.py @@ -7,12 +7,20 @@ import multiprocessing import socket -from collections.abc import Generator +from collections.abc import AsyncGenerator, Generator +from contextlib import asynccontextmanager +from typing import Any import pytest +from starlette.applications import Starlette +from starlette.routing import Mount +import mcp.types as types from mcp.client.session import ClientSession from mcp.client.streamable_http import streamable_http_client +from mcp.server import Server +from mcp.server.streamable_http_manager import StreamableHTTPSessionManager +from mcp.types import TextContent, Tool from tests.test_helpers import wait_for_server # Test constants with various Unicode characters @@ -37,19 +45,8 @@ def run_unicode_server(port: int) -> None: # pragma: no cover """Run the Unicode test server in a separate process.""" - # Import inside the function since this runs in a separate process - from collections.abc import AsyncGenerator - from contextlib import asynccontextmanager - from typing import Any - + # uvicorn is imported inside the function since it's an optional dependency import uvicorn - from starlette.applications import Starlette - from starlette.routing import Mount - - import mcp.types as types - from mcp.server import Server - from mcp.server.streamable_http_manager import StreamableHTTPSessionManager - from mcp.types import TextContent, Tool # Need to recreate the server setup in this process server = Server(name="unicode_test_server") diff --git a/tests/client/test_list_roots_callback.py b/tests/client/test_list_roots_callback.py index 0da0fff07a..887262c586 100644 --- a/tests/client/test_list_roots_callback.py +++ b/tests/client/test_list_roots_callback.py @@ -2,6 +2,7 @@ from pydantic import FileUrl from mcp.client.session import ClientSession +from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.server import Context from mcp.server.session import ServerSession from mcp.shared.context import RequestContext @@ -13,8 +14,6 @@ @pytest.mark.anyio async def test_list_roots_callback(): - from mcp.server.fastmcp import FastMCP - server = FastMCP("test") callback_return = ListRootsResult( diff --git a/tests/client/test_logging_callback.py b/tests/client/test_logging_callback.py index de058eb061..4db551f164 100644 --- a/tests/client/test_logging_callback.py +++ b/tests/client/test_logging_callback.py @@ -3,6 +3,7 @@ import pytest import mcp.types as types +from mcp.server.fastmcp import FastMCP from mcp.shared.memory import ( create_connected_server_and_client_session as create_session, ) @@ -23,8 +24,6 @@ async def __call__(self, params: LoggingMessageNotificationParams) -> None: @pytest.mark.anyio async def test_logging_callback(): - from mcp.server.fastmcp import FastMCP - server = FastMCP("test") logging_collector = LoggingCollector() diff --git a/tests/client/test_output_schema_validation.py b/tests/client/test_output_schema_validation.py index e4a06b7f82..6d67aad3e6 100644 --- a/tests/client/test_output_schema_validation.py +++ b/tests/client/test_output_schema_validation.py @@ -1,8 +1,10 @@ +import inspect import logging from contextlib import contextmanager from typing import Any from unittest.mock import patch +import jsonschema import pytest from mcp.server.lowlevel import Server @@ -19,15 +21,11 @@ def bypass_server_output_validation(): This simulates a malicious or non-compliant server that doesn't validate its outputs, allowing us to test client-side validation. """ - import jsonschema - # Save the original validate function original_validate = jsonschema.validate # Create a mock that tracks which module is calling it def selective_mock(instance: Any = None, schema: Any = None, *args: Any, **kwargs: Any) -> None: - import inspect - # Check the call stack to see where this is being called from for frame_info in inspect.stack(): # If called from the server module, skip validation diff --git a/tests/client/test_sampling_callback.py b/tests/client/test_sampling_callback.py index 733364a767..1c69a95f6d 100644 --- a/tests/client/test_sampling_callback.py +++ b/tests/client/test_sampling_callback.py @@ -1,6 +1,7 @@ import pytest from mcp.client.session import ClientSession +from mcp.server.fastmcp import FastMCP from mcp.shared.context import RequestContext from mcp.shared.memory import ( create_connected_server_and_client_session as create_session, @@ -17,8 +18,6 @@ @pytest.mark.anyio async def test_sampling_callback(): - from mcp.server.fastmcp import FastMCP - server = FastMCP("test") callback_return = CreateMessageResult( @@ -63,8 +62,6 @@ async def test_sampling_tool(message: str): @pytest.mark.anyio async def test_create_message_backwards_compat_single_content(): """Test backwards compatibility: create_message without tools returns single content.""" - from mcp.server.fastmcp import FastMCP - server = FastMCP("test") # Callback returns single content (text) diff --git a/tests/client/test_session_group.py b/tests/client/test_session_group.py index ed07293aed..d0f0a53711 100644 --- a/tests/client/test_session_group.py +++ b/tests/client/test_session_group.py @@ -1,6 +1,7 @@ import contextlib from unittest import mock +import httpx import pytest import mcp @@ -356,8 +357,6 @@ async def test_establish_session_parameterized( assert isinstance(server_params_instance, StreamableHttpParameters) # Verify streamable_http_client was called with url, httpx_client, and terminate_on_close # The http_client is created by the real create_mcp_http_client - import httpx - call_args = mock_specific_client_func.call_args assert call_args.kwargs["url"] == server_params_instance.url assert call_args.kwargs["terminate_on_close"] == server_params_instance.terminate_on_close diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index ba58da7321..b6c60581f6 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -10,7 +10,12 @@ import pytest from mcp.client.session import ClientSession -from mcp.client.stdio import StdioServerParameters, _create_platform_compatible_process, stdio_client +from mcp.client.stdio import ( + StdioServerParameters, + _create_platform_compatible_process, + _terminate_process_tree, + stdio_client, +) from mcp.shared.exceptions import McpError from mcp.shared.message import SessionMessage from mcp.types import CONNECTION_CLOSED, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse @@ -312,8 +317,6 @@ async def test_basic_child_process_cleanup(self): # Terminate using our function print("Terminating process and children...") - from mcp.client.stdio import _terminate_process_tree - await _terminate_process_tree(proc) # Verify processes stopped @@ -413,8 +416,6 @@ async def test_nested_process_tree(self): assert new_size > initial_size, f"{name} process should be writing" # Terminate the whole tree - from mcp.client.stdio import _terminate_process_tree - await _terminate_process_tree(proc) # Verify all stopped @@ -494,8 +495,6 @@ def handle_term(sig, frame): assert size2 > size1, "Child should be writing" # Terminate - this will kill the process group even if parent exits first - from mcp.client.stdio import _terminate_process_tree - await _terminate_process_tree(proc) # Verify child stopped diff --git a/tests/server/auth/test_error_handling.py b/tests/server/auth/test_error_handling.py index cbc333441d..436bae05af 100644 --- a/tests/server/auth/test_error_handling.py +++ b/tests/server/auth/test_error_handling.py @@ -2,6 +2,9 @@ Tests for OAuth error handling in the auth handlers. """ +import base64 +import hashlib +import secrets import unittest.mock from typing import Any from urllib.parse import parse_qs, urlparse @@ -14,6 +17,7 @@ from mcp.server.auth.provider import AuthorizeError, RegistrationError, TokenError from mcp.server.auth.routes import create_auth_routes +from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions from tests.server.fastmcp.auth.test_auth_integration import MockOAuthProvider @@ -25,8 +29,6 @@ def oauth_provider(): @pytest.fixture def app(oauth_provider: MockOAuthProvider): - from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions - # Enable client registration client_registration_options = ClientRegistrationOptions(enabled=True) revocation_options = RevocationOptions(enabled=True) @@ -53,10 +55,6 @@ def client(app: Starlette): @pytest.fixture def pkce_challenge(): """Create a PKCE challenge with code_verifier and code_challenge.""" - import base64 - import hashlib - import secrets - # Generate a code verifier code_verifier = secrets.token_urlsafe(64)[:128] diff --git a/tests/server/auth/test_protected_resource.py b/tests/server/auth/test_protected_resource.py index 82af16c5b1..bf6502a302 100644 --- a/tests/server/auth/test_protected_resource.py +++ b/tests/server/auth/test_protected_resource.py @@ -2,6 +2,8 @@ Integration tests for MCP Oauth Protected Resource. """ +from urllib.parse import urlparse + import httpx import pytest from inline_snapshot import snapshot @@ -159,8 +161,6 @@ def test_route_path_matches_metadata_url(self): ) # Extract path from metadata URL - from urllib.parse import urlparse - metadata_path = urlparse(str(metadata_url)).path # Verify consistency @@ -181,8 +181,6 @@ def test_consistent_paths_for_various_resources(self, resource_url: str, expecte # Test URL generation metadata_url = build_resource_metadata_url(resource_url_obj) - from urllib.parse import urlparse - url_path = urlparse(str(metadata_url)).path # Test route creation diff --git a/tests/server/fastmcp/auth/test_auth_integration.py b/tests/server/fastmcp/auth/test_auth_integration.py index 953d59aa14..6a1605bdb4 100644 --- a/tests/server/fastmcp/auth/test_auth_integration.py +++ b/tests/server/fastmcp/auth/test_auth_integration.py @@ -1258,8 +1258,6 @@ async def test_basic_auth_no_colon_fails( ) # Send base64 without colon (invalid format) - import base64 - invalid_creds = base64.b64encode(b"no-colon-here").decode() response = await test_client.post( "/token", @@ -1306,8 +1304,6 @@ async def test_basic_auth_client_id_mismatch_fails( ) # Send different client_id in Basic auth header - import base64 - wrong_creds = base64.b64encode(f"wrong-client-id:{client_info['client_secret']}".encode()).decode() response = await test_client.post( "/token", diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py index 61e524290e..9f574f1d08 100644 --- a/tests/server/fastmcp/test_func_metadata.py +++ b/tests/server/fastmcp/test_func_metadata.py @@ -5,13 +5,14 @@ # pyright: reportUnknownLambdaType=false from collections.abc import Callable from dataclasses import dataclass -from typing import Annotated, Any, Final, TypedDict +from typing import Annotated, Any, Final, NamedTuple, TypedDict import annotated_types import pytest from dirty_equals import IsPartialDict from pydantic import BaseModel, Field +from mcp.server.fastmcp.exceptions import InvalidSignature from mcp.server.fastmcp.utilities.func_metadata import func_metadata from mcp.types import CallToolResult @@ -558,7 +559,6 @@ def handle_json_payload(payload: str, strict_mode: bool = False) -> str: # prag def test_structured_output_requires_return_annotation(): """Test that structured_output=True requires a return annotation""" - from mcp.server.fastmcp.exceptions import InvalidSignature def func_no_annotation(): # pragma: no cover return "hello" @@ -881,8 +881,6 @@ def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, Per def test_tool_call_result_in_optional_is_rejected(): """Test that Optional[CallToolResult] raises InvalidSignature""" - from mcp.server.fastmcp.exceptions import InvalidSignature - def func_optional_call_tool_result() -> CallToolResult | None: # pragma: no cover return CallToolResult(content=[]) @@ -896,8 +894,6 @@ def func_optional_call_tool_result() -> CallToolResult | None: # pragma: no cov def test_tool_call_result_in_union_is_rejected(): """Test that Union[str, CallToolResult] raises InvalidSignature""" - from mcp.server.fastmcp.exceptions import InvalidSignature - def func_union_call_tool_result() -> str | CallToolResult: # pragma: no cover return CallToolResult(content=[]) @@ -910,7 +906,6 @@ def func_union_call_tool_result() -> str | CallToolResult: # pragma: no cover def test_tool_call_result_in_pipe_union_is_rejected(): """Test that str | CallToolResult raises InvalidSignature""" - from mcp.server.fastmcp.exceptions import InvalidSignature def func_pipe_union_call_tool_result() -> str | CallToolResult: # pragma: no cover return CallToolResult(content=[]) @@ -985,9 +980,6 @@ def func_nested() -> PersonWithAddress: # pragma: no cover def test_structured_output_unserializable_type_error(): """Test error when structured_output=True is used with unserializable types""" - from typing import NamedTuple - - from mcp.server.fastmcp.exceptions import InvalidSignature # Test with a class that has non-serializable default values class ConfigWithCallable: @@ -1185,8 +1177,6 @@ def func_with_reserved_json( # pragma: no cover def test_disallowed_type_qualifier(): - from mcp.server.fastmcp.exceptions import InvalidSignature - def func_disallowed_qualifier() -> Final[int]: # type: ignore pass # pragma: no cover diff --git a/tests/server/fastmcp/test_integration.py b/tests/server/fastmcp/test_integration.py index 70948bd7e2..8d75f08d90 100644 --- a/tests/server/fastmcp/test_integration.py +++ b/tests/server/fastmcp/test_integration.py @@ -51,8 +51,10 @@ NotificationParams, ProgressNotification, ProgressNotificationParams, + PromptReference, ReadResourceResult, ResourceListChangedNotification, + ResourceTemplateReference, ServerNotification, ServerRequest, TextContent, @@ -583,8 +585,6 @@ async def test_completion(server_transport: str, server_url: str) -> None: assert result.capabilities.prompts is not None # Test resource completion - from mcp.types import ResourceTemplateReference - completion_result = await session.complete( ref=ResourceTemplateReference(type="ref/resource", uri="github://repos/{owner}/{repo}"), argument={"name": "repo", "value": ""}, @@ -600,8 +600,6 @@ async def test_completion(server_transport: str, server_url: str) -> None: assert "specification" in completion_result.completion.values # Test prompt completion - from mcp.types import PromptReference - completion_result = await session.complete( ref=PromptReference(type="ref/prompt", name="review_code"), argument={"name": "language", "value": "py"}, @@ -644,8 +642,6 @@ async def test_fastmcp_quickstart(server_transport: str, server_url: str) -> Non assert tool_result.content[0].text == "30" # Test greeting resource directly - from pydantic import AnyUrl - resource_result = await session.read_resource(AnyUrl("greeting://Alice")) assert len(resource_result.contents) == 1 assert isinstance(resource_result.contents[0], TextResourceContents) diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py index 87637fcb8a..2eb1930991 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -5,9 +5,11 @@ import pytest from pydantic import BaseModel +from starlette.applications import Starlette from starlette.routing import Mount, Route from mcp.server.fastmcp import Context, FastMCP +from mcp.server.fastmcp.exceptions import ToolError from mcp.server.fastmcp.prompts.base import Message, UserMessage from mcp.server.fastmcp.resources import FileResource, FunctionResource from mcp.server.fastmcp.utilities.types import Audio, Image @@ -51,8 +53,6 @@ async def test_create_server(self): @pytest.mark.anyio async def test_sse_app_returns_starlette_app(self): """Test that sse_app returns a Starlette application with correct routes.""" - from starlette.applications import Starlette - mcp = FastMCP("test", host="0.0.0.0") # Use 0.0.0.0 to avoid auto DNS protection app = mcp.sse_app() @@ -616,8 +616,6 @@ async def test_remove_tool(self): @pytest.mark.anyio async def test_remove_nonexistent_tool(self): """Test that removing a non-existent tool raises ToolError.""" - from mcp.server.fastmcp.exceptions import ToolError - mcp = FastMCP() with pytest.raises(ToolError, match="Unknown tool: nonexistent"): diff --git a/tests/server/fastmcp/test_url_elicitation.py b/tests/server/fastmcp/test_url_elicitation.py index a4d3b2e643..4d4e4b6ca6 100644 --- a/tests/server/fastmcp/test_url_elicitation.py +++ b/tests/server/fastmcp/test_url_elicitation.py @@ -2,10 +2,11 @@ import anyio import pytest +from pydantic import BaseModel, Field from mcp import types from mcp.client.session import ClientSession -from mcp.server.elicitation import CancelledElicitation, DeclinedElicitation +from mcp.server.elicitation import CancelledElicitation, DeclinedElicitation, elicit_url from mcp.server.fastmcp import Context, FastMCP from mcp.server.session import ServerSession from mcp.shared.context import RequestContext @@ -110,8 +111,6 @@ async def elicitation_callback(context: RequestContext[ClientSession, None], par @pytest.mark.anyio async def test_url_elicitation_helper_function(): """Test the elicit_url helper function.""" - from mcp.server.elicitation import elicit_url - mcp = FastMCP(name="URLElicitationHelperServer") @mcp.tool(description="Tool using elicit_url helper") @@ -180,8 +179,6 @@ async def elicitation_callback(context: RequestContext[ClientSession, None], par @pytest.mark.anyio async def test_form_mode_still_works(): """Ensure form mode elicitation still works after SEP 1036.""" - from pydantic import BaseModel, Field - mcp = FastMCP(name="FormModeBackwardCompatServer") class NameSchema(BaseModel): @@ -267,8 +264,6 @@ async def test_url_elicitation_required_error_code(): @pytest.mark.anyio async def test_elicit_url_typed_results(): """Test that elicit_url returns properly typed result objects.""" - from mcp.server.elicitation import elicit_url - mcp = FastMCP(name="TypedResultsServer") @mcp.tool(description="Test declined result") @@ -329,8 +324,6 @@ async def cancel_callback(context: RequestContext[ClientSession, None], params: @pytest.mark.anyio async def test_deprecated_elicit_method(): """Test the deprecated elicit() method for backward compatibility.""" - from pydantic import BaseModel, Field - mcp = FastMCP(name="DeprecatedElicitServer") class EmailSchema(BaseModel): diff --git a/tests/shared/test_auth_utils.py b/tests/shared/test_auth_utils.py index 5b12dc6775..d658385cb9 100644 --- a/tests/shared/test_auth_utils.py +++ b/tests/shared/test_auth_utils.py @@ -1,5 +1,7 @@ """Tests for OAuth 2.0 Resource Indicators utilities.""" +from pydantic import HttpUrl + from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url @@ -37,8 +39,6 @@ def test_lowercase_scheme_and_host(self): def test_handles_pydantic_urls(self): """Should handle Pydantic URL types.""" - from pydantic import HttpUrl - url = HttpUrl("https://example.com/path") assert resource_url_from_server_url(url) == "https://example.com/path" diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 795bd9705e..68c0b644be 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -10,6 +10,7 @@ import multiprocessing import socket import time +import traceback from collections.abc import Generator from typing import Any from unittest.mock import MagicMock @@ -462,8 +463,6 @@ def run_server( try: server.run() except Exception: - import traceback - traceback.print_exc() @@ -1100,8 +1099,6 @@ async def test_streamable_http_client_json_response(json_response_server: None, @pytest.mark.anyio async def test_streamable_http_client_get_stream(basic_server: None, basic_server_url: str): """Test GET stream functionality for server-initiated messages.""" - import mcp.types as types - notifications_received: list[types.ServerNotification] = [] # Define message handler to capture notifications diff --git a/tests/shared/test_ws.py b/tests/shared/test_ws.py index e24063ffc9..51e37348e4 100644 --- a/tests/shared/test_ws.py +++ b/tests/shared/test_ws.py @@ -3,6 +3,7 @@ import time from collections.abc import AsyncGenerator, Generator from typing import Any +from urllib.parse import urlparse import anyio import pytest @@ -50,8 +51,6 @@ def __init__(self): @self.read_resource() async def handle_read_resource(uri: str) -> str | bytes: - from urllib.parse import urlparse - parsed = urlparse(uri) if parsed.scheme == "foobar": return f"Read {parsed.netloc}" diff --git a/tests/test_examples.py b/tests/test_examples.py index 6f5464e394..2c8c9d17dd 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -6,10 +6,16 @@ # pyright: reportUnknownMemberType=false import sys +from pathlib import Path import pytest +from pydantic import AnyUrl from pytest_examples import CodeExample, EvalExample, find_examples +from examples.fastmcp.complex_inputs import mcp as complex_inputs_mcp +from examples.fastmcp.desktop import mcp as desktop_mcp +from examples.fastmcp.direct_call_tool_result_return import mcp as direct_call_tool_result_mcp +from examples.fastmcp.simple_echo import mcp as simple_echo_mcp from mcp.shared.memory import create_connected_server_and_client_session as client_session from mcp.types import TextContent, TextResourceContents @@ -17,9 +23,7 @@ @pytest.mark.anyio async def test_simple_echo(): """Test the simple echo server""" - from examples.fastmcp.simple_echo import mcp - - async with client_session(mcp._mcp_server) as client: + async with client_session(simple_echo_mcp._mcp_server) as client: result = await client.call_tool("echo", {"text": "hello"}) assert len(result.content) == 1 content = result.content[0] @@ -30,9 +34,7 @@ async def test_simple_echo(): @pytest.mark.anyio async def test_complex_inputs(): """Test the complex inputs server""" - from examples.fastmcp.complex_inputs import mcp - - async with client_session(mcp._mcp_server) as client: + async with client_session(complex_inputs_mcp._mcp_server) as client: tank = {"shrimp": [{"name": "bob"}, {"name": "alice"}]} result = await client.call_tool("name_shrimp", {"tank": tank, "extra_names": ["charlie"]}) assert len(result.content) == 3 @@ -47,9 +49,7 @@ async def test_complex_inputs(): @pytest.mark.anyio async def test_direct_call_tool_result_return(): """Test the CallToolResult echo server""" - from examples.fastmcp.direct_call_tool_result_return import mcp - - async with client_session(mcp._mcp_server) as client: + async with client_session(direct_call_tool_result_mcp._mcp_server) as client: result = await client.call_tool("echo", {"text": "hello"}) assert len(result.content) == 1 content = result.content[0] @@ -64,18 +64,12 @@ async def test_direct_call_tool_result_return(): @pytest.mark.anyio async def test_desktop(monkeypatch: pytest.MonkeyPatch): """Test the desktop server""" - from pathlib import Path - - from pydantic import AnyUrl - - from examples.fastmcp.desktop import mcp - # Mock desktop directory listing mock_files = [Path("/fake/path/file1.txt"), Path("/fake/path/file2.txt")] monkeypatch.setattr(Path, "iterdir", lambda self: mock_files) # type: ignore[reportUnknownArgumentType] monkeypatch.setattr(Path, "home", lambda: Path("/fake/home")) - async with client_session(mcp._mcp_server) as client: + async with client_session(desktop_mcp._mcp_server) as client: # Test the sum function result = await client.call_tool("sum", {"a": 1, "b": 2}) assert len(result.content) == 1 From 468f680315e531408146e0f51cf0991a2b17a46e Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:22:46 +0100 Subject: [PATCH 2/3] revert: undo server.py changes to avoid conflicts Reverting changes to src/mcp/server/fastmcp/server.py as these are being handled in a separate branch. Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 0 Claude-Permission-Prompts: 1 Claude-Escapes: 0 --- src/mcp/server/fastmcp/server.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 5e50e892fc..0d18df1131 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -24,11 +24,6 @@ from mcp.server.auth.middleware.auth_context import AuthContextMiddleware from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier -from mcp.server.auth.routes import ( - build_resource_metadata_url, - create_auth_routes, - create_protected_resource_routes, -) from mcp.server.auth.settings import AuthSettings from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, UrlElicitationResult, elicit_with_validation from mcp.server.elicitation import elicit_url as _elicit_url @@ -815,6 +810,8 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no # Add auth endpoints if auth server provider is configured if self._auth_server_provider: + from mcp.server.auth.routes import create_auth_routes + routes.extend( create_auth_routes( provider=self._auth_server_provider, @@ -830,6 +827,8 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no # Determine resource metadata URL resource_metadata_url = None if self.settings.auth and self.settings.auth.resource_server_url: + from mcp.server.auth.routes import build_resource_metadata_url + # Build compliant metadata URL for WWW-Authenticate header resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) @@ -869,6 +868,8 @@ async def sse_endpoint(request: Request) -> Response: ) # Add protected resource metadata endpoint if configured as RS if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover + from mcp.server.auth.routes import create_protected_resource_routes + routes.extend( create_protected_resource_routes( resource_url=self.settings.auth.resource_server_url, @@ -885,6 +886,7 @@ async def sse_endpoint(request: Request) -> Response: def streamable_http_app(self) -> Starlette: """Return an instance of the StreamableHTTP server app.""" + from starlette.middleware import Middleware # Create session manager on first call (lazy initialization) if self._session_manager is None: # pragma: no branch @@ -921,6 +923,8 @@ def streamable_http_app(self) -> Starlette: # Add auth endpoints if auth server provider is configured if self._auth_server_provider: + from mcp.server.auth.routes import create_auth_routes + routes.extend( create_auth_routes( provider=self._auth_server_provider, @@ -936,6 +940,8 @@ def streamable_http_app(self) -> Starlette: # Determine resource metadata URL resource_metadata_url = None if self.settings.auth and self.settings.auth.resource_server_url: + from mcp.server.auth.routes import build_resource_metadata_url + # Build compliant metadata URL for WWW-Authenticate header resource_metadata_url = build_resource_metadata_url(self.settings.auth.resource_server_url) @@ -956,6 +962,8 @@ def streamable_http_app(self) -> Starlette: # Add protected resource metadata endpoint if configured as RS if self.settings.auth and self.settings.auth.resource_server_url: # pragma: no cover + from mcp.server.auth.routes import create_protected_resource_routes + routes.extend( create_protected_resource_routes( resource_url=self.settings.auth.resource_server_url, From cfb1e543572a5c540f916dbcfda2535f2e89e22f Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:50:30 +0100 Subject: [PATCH 3/3] Update tests/client/test_http_unicode.py Co-authored-by: Marcelo Trylesinski --- tests/client/test_http_unicode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/client/test_http_unicode.py b/tests/client/test_http_unicode.py index 774b832caa..804fe1d354 100644 --- a/tests/client/test_http_unicode.py +++ b/tests/client/test_http_unicode.py @@ -45,7 +45,6 @@ def run_unicode_server(port: int) -> None: # pragma: no cover """Run the Unicode test server in a separate process.""" - # uvicorn is imported inside the function since it's an optional dependency import uvicorn # Need to recreate the server setup in this process