refactor: move inline imports to module level (#1893)
This commit is contained in:
parent
c06362126b
commit
5d80f4efc8
24 changed files with 58 additions and 122 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
@ -15,8 +16,6 @@ from mcp.shared.message import SessionMessage
|
||||||
from mcp.shared.session import RequestResponder
|
from mcp.shared.session import RequestResponder
|
||||||
|
|
||||||
if not sys.warnoptions:
|
if not sys.warnoptions:
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ Provides OAuth providers for machine-to-machine authentication flows:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
@ -409,8 +410,6 @@ class RFC7523OAuthClientProvider(OAuthClientProvider):
|
||||||
timeout: float = 300.0,
|
timeout: float = 300.0,
|
||||||
jwt_parameters: JWTParameters | None = None,
|
jwt_parameters: JWTParameters | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"RFC7523OAuthClientProvider is deprecated. Use ClientCredentialsOAuthProvider "
|
"RFC7523OAuthClientProvider is deprecated. Use ClientCredentialsOAuthProvider "
|
||||||
"or PrivateKeyJWTOAuthProvider instead.",
|
"or PrivateKeyJWTOAuthProvider instead.",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
|
|
||||||
|
|
@ -10,8 +11,6 @@ from mcp.server.stdio import stdio_server
|
||||||
from mcp.types import ServerCapabilities
|
from mcp.types import ServerCapabilities
|
||||||
|
|
||||||
if not sys.warnoptions:
|
if not sys.warnoptions:
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ import logging
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
|
||||||
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
|
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
|
||||||
|
from importlib.metadata import version as importlib_version
|
||||||
from typing import Any, Generic, TypeAlias, cast
|
from typing import Any, Generic, TypeAlias, cast
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
|
|
@ -173,9 +174,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
|
|
||||||
def pkg_version(package: str) -> str:
|
def pkg_version(package: str) -> str:
|
||||||
try:
|
try:
|
||||||
from importlib.metadata import version
|
return importlib_version(package)
|
||||||
|
|
||||||
return version(package)
|
|
||||||
except Exception: # pragma: no cover
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import warnings
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -60,8 +61,6 @@ def rfc7523_oauth_provider(client_metadata: OAuthClientMetadata, mock_storage: M
|
||||||
"""Mock callback handler."""
|
"""Mock callback handler."""
|
||||||
return "test_auth_code", "test_state"
|
return "test_auth_code", "test_state"
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
return RFC7523OAuthClientProvider(
|
return RFC7523OAuthClientProvider(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Tests for refactored OAuth client authentication implementation.
|
||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import unquote
|
from urllib.parse import parse_qs, quote, unquote, urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -27,6 +27,8 @@ from mcp.client.auth.utils import (
|
||||||
is_valid_client_metadata_url,
|
is_valid_client_metadata_url,
|
||||||
should_use_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 (
|
from mcp.shared.auth import (
|
||||||
OAuthClientInformationFull,
|
OAuthClientInformationFull,
|
||||||
OAuthClientMetadata,
|
OAuthClientMetadata,
|
||||||
|
|
@ -758,8 +760,6 @@ class TestProtectedResourceMetadata:
|
||||||
content = request.content.decode()
|
content = request.content.decode()
|
||||||
assert "resource=" in content
|
assert "resource=" in content
|
||||||
# Check URL-encoded resource parameter
|
# Check URL-encoded resource parameter
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
expected_resource = quote(oauth_provider.context.get_resource_url(), safe="")
|
expected_resource = quote(oauth_provider.context.get_resource_url(), safe="")
|
||||||
assert f"resource={expected_resource}" in content
|
assert f"resource={expected_resource}" in content
|
||||||
|
|
||||||
|
|
@ -1226,8 +1226,6 @@ class TestAuthFlow:
|
||||||
"%3A", ":"
|
"%3A", ":"
|
||||||
).replace("+", " ")
|
).replace("+", " ")
|
||||||
# Extract state from redirect URL
|
# Extract state from redirect URL
|
||||||
from urllib.parse import parse_qs, urlparse
|
|
||||||
|
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
params = parse_qs(parsed.query)
|
params = parse_qs(parsed.query)
|
||||||
captured_state = params.get("state", [None])[0]
|
captured_state = params.get("state", [None])[0]
|
||||||
|
|
@ -1336,9 +1334,6 @@ def test_build_metadata(
|
||||||
registration_endpoint: str,
|
registration_endpoint: str,
|
||||||
revocation_endpoint: str,
|
revocation_endpoint: str,
|
||||||
):
|
):
|
||||||
from mcp.server.auth.routes import build_metadata
|
|
||||||
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
|
|
||||||
|
|
||||||
metadata = build_metadata(
|
metadata = build_metadata(
|
||||||
issuer_url=AnyHttpUrl(issuer_url),
|
issuer_url=AnyHttpUrl(issuer_url),
|
||||||
service_documentation_url=AnyHttpUrl(service_documentation_url),
|
service_documentation_url=AnyHttpUrl(service_documentation_url),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,20 @@ Verifies that Unicode text is correctly transmitted and received in both directi
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import socket
|
import socket
|
||||||
from collections.abc import Generator
|
from collections.abc import AsyncGenerator, Generator
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
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.session import ClientSession
|
||||||
from mcp.client.streamable_http import streamable_http_client
|
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
|
from tests.test_helpers import wait_for_server
|
||||||
|
|
||||||
# Test constants with various Unicode characters
|
# Test constants with various Unicode characters
|
||||||
|
|
@ -37,19 +45,7 @@ UNICODE_TEST_STRINGS = {
|
||||||
|
|
||||||
def run_unicode_server(port: int) -> None: # pragma: no cover
|
def run_unicode_server(port: int) -> None: # pragma: no cover
|
||||||
"""Run the Unicode test server in a separate process."""
|
"""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
|
|
||||||
|
|
||||||
import uvicorn
|
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
|
# Need to recreate the server setup in this process
|
||||||
server = Server(name="unicode_test_server")
|
server = Server(name="unicode_test_server")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import pytest
|
||||||
from pydantic import FileUrl
|
from pydantic import FileUrl
|
||||||
|
|
||||||
from mcp.client.session import ClientSession
|
from mcp.client.session import ClientSession
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
from mcp.server.fastmcp.server import Context
|
from mcp.server.fastmcp.server import Context
|
||||||
from mcp.server.session import ServerSession
|
from mcp.server.session import ServerSession
|
||||||
from mcp.shared.context import RequestContext
|
from mcp.shared.context import RequestContext
|
||||||
|
|
@ -13,8 +14,6 @@ from mcp.types import ListRootsResult, Root, TextContent
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_list_roots_callback():
|
async def test_list_roots_callback():
|
||||||
from mcp.server.fastmcp import FastMCP
|
|
||||||
|
|
||||||
server = FastMCP("test")
|
server = FastMCP("test")
|
||||||
|
|
||||||
callback_return = ListRootsResult(
|
callback_return = ListRootsResult(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Any, Literal
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mcp.types as types
|
import mcp.types as types
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
from mcp.shared.memory import (
|
from mcp.shared.memory import (
|
||||||
create_connected_server_and_client_session as create_session,
|
create_connected_server_and_client_session as create_session,
|
||||||
)
|
)
|
||||||
|
|
@ -23,8 +24,6 @@ class LoggingCollector:
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_logging_callback():
|
async def test_logging_callback():
|
||||||
from mcp.server.fastmcp import FastMCP
|
|
||||||
|
|
||||||
server = FastMCP("test")
|
server = FastMCP("test")
|
||||||
logging_collector = LoggingCollector()
|
logging_collector = LoggingCollector()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mcp.server.lowlevel import Server
|
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
|
This simulates a malicious or non-compliant server that doesn't validate
|
||||||
its outputs, allowing us to test client-side validation.
|
its outputs, allowing us to test client-side validation.
|
||||||
"""
|
"""
|
||||||
import jsonschema
|
|
||||||
|
|
||||||
# Save the original validate function
|
# Save the original validate function
|
||||||
original_validate = jsonschema.validate
|
original_validate = jsonschema.validate
|
||||||
|
|
||||||
# Create a mock that tracks which module is calling it
|
# Create a mock that tracks which module is calling it
|
||||||
def selective_mock(instance: Any = None, schema: Any = None, *args: Any, **kwargs: Any) -> None:
|
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
|
# Check the call stack to see where this is being called from
|
||||||
for frame_info in inspect.stack():
|
for frame_info in inspect.stack():
|
||||||
# If called from the server module, skip validation
|
# If called from the server module, skip validation
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mcp.client.session import ClientSession
|
from mcp.client.session import ClientSession
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
from mcp.shared.context import RequestContext
|
from mcp.shared.context import RequestContext
|
||||||
from mcp.shared.memory import (
|
from mcp.shared.memory import (
|
||||||
create_connected_server_and_client_session as create_session,
|
create_connected_server_and_client_session as create_session,
|
||||||
|
|
@ -17,8 +18,6 @@ from mcp.types import (
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_sampling_callback():
|
async def test_sampling_callback():
|
||||||
from mcp.server.fastmcp import FastMCP
|
|
||||||
|
|
||||||
server = FastMCP("test")
|
server = FastMCP("test")
|
||||||
|
|
||||||
callback_return = CreateMessageResult(
|
callback_return = CreateMessageResult(
|
||||||
|
|
@ -63,8 +62,6 @@ async def test_sampling_callback():
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_create_message_backwards_compat_single_content():
|
async def test_create_message_backwards_compat_single_content():
|
||||||
"""Test backwards compatibility: create_message without tools returns single content."""
|
"""Test backwards compatibility: create_message without tools returns single content."""
|
||||||
from mcp.server.fastmcp import FastMCP
|
|
||||||
|
|
||||||
server = FastMCP("test")
|
server = FastMCP("test")
|
||||||
|
|
||||||
# Callback returns single content (text)
|
# Callback returns single content (text)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mcp
|
import mcp
|
||||||
|
|
@ -356,8 +357,6 @@ class TestClientSessionGroup:
|
||||||
assert isinstance(server_params_instance, StreamableHttpParameters)
|
assert isinstance(server_params_instance, StreamableHttpParameters)
|
||||||
# Verify streamable_http_client was called with url, httpx_client, and terminate_on_close
|
# 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
|
# The http_client is created by the real create_mcp_http_client
|
||||||
import httpx
|
|
||||||
|
|
||||||
call_args = mock_specific_client_func.call_args
|
call_args = mock_specific_client_func.call_args
|
||||||
assert call_args.kwargs["url"] == server_params_instance.url
|
assert call_args.kwargs["url"] == server_params_instance.url
|
||||||
assert call_args.kwargs["terminate_on_close"] == server_params_instance.terminate_on_close
|
assert call_args.kwargs["terminate_on_close"] == server_params_instance.terminate_on_close
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,12 @@ import anyio
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mcp.client.session import ClientSession
|
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.exceptions import McpError
|
||||||
from mcp.shared.message import SessionMessage
|
from mcp.shared.message import SessionMessage
|
||||||
from mcp.types import CONNECTION_CLOSED, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
|
from mcp.types import CONNECTION_CLOSED, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
|
||||||
|
|
@ -312,8 +317,6 @@ class TestChildProcessCleanup:
|
||||||
|
|
||||||
# Terminate using our function
|
# Terminate using our function
|
||||||
print("Terminating process and children...")
|
print("Terminating process and children...")
|
||||||
from mcp.client.stdio import _terminate_process_tree
|
|
||||||
|
|
||||||
await _terminate_process_tree(proc)
|
await _terminate_process_tree(proc)
|
||||||
|
|
||||||
# Verify processes stopped
|
# Verify processes stopped
|
||||||
|
|
@ -413,8 +416,6 @@ class TestChildProcessCleanup:
|
||||||
assert new_size > initial_size, f"{name} process should be writing"
|
assert new_size > initial_size, f"{name} process should be writing"
|
||||||
|
|
||||||
# Terminate the whole tree
|
# Terminate the whole tree
|
||||||
from mcp.client.stdio import _terminate_process_tree
|
|
||||||
|
|
||||||
await _terminate_process_tree(proc)
|
await _terminate_process_tree(proc)
|
||||||
|
|
||||||
# Verify all stopped
|
# Verify all stopped
|
||||||
|
|
@ -494,8 +495,6 @@ class TestChildProcessCleanup:
|
||||||
assert size2 > size1, "Child should be writing"
|
assert size2 > size1, "Child should be writing"
|
||||||
|
|
||||||
# Terminate - this will kill the process group even if parent exits first
|
# 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)
|
await _terminate_process_tree(proc)
|
||||||
|
|
||||||
# Verify child stopped
|
# Verify child stopped
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
Tests for OAuth error handling in the auth handlers.
|
Tests for OAuth error handling in the auth handlers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import secrets
|
||||||
import unittest.mock
|
import unittest.mock
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
@ -14,6 +17,7 @@ from starlette.applications import Starlette
|
||||||
|
|
||||||
from mcp.server.auth.provider import AuthorizeError, RegistrationError, TokenError
|
from mcp.server.auth.provider import AuthorizeError, RegistrationError, TokenError
|
||||||
from mcp.server.auth.routes import create_auth_routes
|
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
|
from tests.server.fastmcp.auth.test_auth_integration import MockOAuthProvider
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,8 +29,6 @@ def oauth_provider():
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app(oauth_provider: MockOAuthProvider):
|
def app(oauth_provider: MockOAuthProvider):
|
||||||
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
|
|
||||||
|
|
||||||
# Enable client registration
|
# Enable client registration
|
||||||
client_registration_options = ClientRegistrationOptions(enabled=True)
|
client_registration_options = ClientRegistrationOptions(enabled=True)
|
||||||
revocation_options = RevocationOptions(enabled=True)
|
revocation_options = RevocationOptions(enabled=True)
|
||||||
|
|
@ -53,10 +55,6 @@ def client(app: Starlette):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def pkce_challenge():
|
def pkce_challenge():
|
||||||
"""Create a PKCE challenge with code_verifier and code_challenge."""
|
"""Create a PKCE challenge with code_verifier and code_challenge."""
|
||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import secrets
|
|
||||||
|
|
||||||
# Generate a code verifier
|
# Generate a code verifier
|
||||||
code_verifier = secrets.token_urlsafe(64)[:128]
|
code_verifier = secrets.token_urlsafe(64)[:128]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
Integration tests for MCP Oauth Protected Resource.
|
Integration tests for MCP Oauth Protected Resource.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
from inline_snapshot import snapshot
|
from inline_snapshot import snapshot
|
||||||
|
|
@ -159,8 +161,6 @@ class TestRouteConsistency:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract path from metadata URL
|
# Extract path from metadata URL
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
metadata_path = urlparse(str(metadata_url)).path
|
metadata_path = urlparse(str(metadata_url)).path
|
||||||
|
|
||||||
# Verify consistency
|
# Verify consistency
|
||||||
|
|
@ -181,8 +181,6 @@ class TestRouteConsistency:
|
||||||
|
|
||||||
# Test URL generation
|
# Test URL generation
|
||||||
metadata_url = build_resource_metadata_url(resource_url_obj)
|
metadata_url = build_resource_metadata_url(resource_url_obj)
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
url_path = urlparse(str(metadata_url)).path
|
url_path = urlparse(str(metadata_url)).path
|
||||||
|
|
||||||
# Test route creation
|
# Test route creation
|
||||||
|
|
|
||||||
|
|
@ -1258,8 +1258,6 @@ class TestAuthEndpoints:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send base64 without colon (invalid format)
|
# Send base64 without colon (invalid format)
|
||||||
import base64
|
|
||||||
|
|
||||||
invalid_creds = base64.b64encode(b"no-colon-here").decode()
|
invalid_creds = base64.b64encode(b"no-colon-here").decode()
|
||||||
response = await test_client.post(
|
response = await test_client.post(
|
||||||
"/token",
|
"/token",
|
||||||
|
|
@ -1306,8 +1304,6 @@ class TestAuthEndpoints:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send different client_id in Basic auth header
|
# Send different client_id in Basic auth header
|
||||||
import base64
|
|
||||||
|
|
||||||
wrong_creds = base64.b64encode(f"wrong-client-id:{client_info['client_secret']}".encode()).decode()
|
wrong_creds = base64.b64encode(f"wrong-client-id:{client_info['client_secret']}".encode()).decode()
|
||||||
response = await test_client.post(
|
response = await test_client.post(
|
||||||
"/token",
|
"/token",
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@
|
||||||
# pyright: reportUnknownLambdaType=false
|
# pyright: reportUnknownLambdaType=false
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Annotated, Any, Final, TypedDict
|
from typing import Annotated, Any, Final, NamedTuple, TypedDict
|
||||||
|
|
||||||
import annotated_types
|
import annotated_types
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsPartialDict
|
from dirty_equals import IsPartialDict
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from mcp.server.fastmcp.exceptions import InvalidSignature
|
||||||
from mcp.server.fastmcp.utilities.func_metadata import func_metadata
|
from mcp.server.fastmcp.utilities.func_metadata import func_metadata
|
||||||
from mcp.types import CallToolResult
|
from mcp.types import CallToolResult
|
||||||
|
|
||||||
|
|
@ -558,7 +559,6 @@ async def test_str_annotation_runtime_validation():
|
||||||
|
|
||||||
def test_structured_output_requires_return_annotation():
|
def test_structured_output_requires_return_annotation():
|
||||||
"""Test that structured_output=True requires a 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
|
def func_no_annotation(): # pragma: no cover
|
||||||
return "hello"
|
return "hello"
|
||||||
|
|
@ -881,8 +881,6 @@ def test_tool_call_result_annotated_is_structured_and_invalid():
|
||||||
def test_tool_call_result_in_optional_is_rejected():
|
def test_tool_call_result_in_optional_is_rejected():
|
||||||
"""Test that Optional[CallToolResult] raises InvalidSignature"""
|
"""Test that Optional[CallToolResult] raises InvalidSignature"""
|
||||||
|
|
||||||
from mcp.server.fastmcp.exceptions import InvalidSignature
|
|
||||||
|
|
||||||
def func_optional_call_tool_result() -> CallToolResult | None: # pragma: no cover
|
def func_optional_call_tool_result() -> CallToolResult | None: # pragma: no cover
|
||||||
return CallToolResult(content=[])
|
return CallToolResult(content=[])
|
||||||
|
|
||||||
|
|
@ -896,8 +894,6 @@ def test_tool_call_result_in_optional_is_rejected():
|
||||||
def test_tool_call_result_in_union_is_rejected():
|
def test_tool_call_result_in_union_is_rejected():
|
||||||
"""Test that Union[str, CallToolResult] raises InvalidSignature"""
|
"""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
|
def func_union_call_tool_result() -> str | CallToolResult: # pragma: no cover
|
||||||
return CallToolResult(content=[])
|
return CallToolResult(content=[])
|
||||||
|
|
||||||
|
|
@ -910,7 +906,6 @@ def test_tool_call_result_in_union_is_rejected():
|
||||||
|
|
||||||
def test_tool_call_result_in_pipe_union_is_rejected():
|
def test_tool_call_result_in_pipe_union_is_rejected():
|
||||||
"""Test that str | CallToolResult raises InvalidSignature"""
|
"""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
|
def func_pipe_union_call_tool_result() -> str | CallToolResult: # pragma: no cover
|
||||||
return CallToolResult(content=[])
|
return CallToolResult(content=[])
|
||||||
|
|
@ -985,9 +980,6 @@ def test_structured_output_nested_models():
|
||||||
|
|
||||||
def test_structured_output_unserializable_type_error():
|
def test_structured_output_unserializable_type_error():
|
||||||
"""Test error when structured_output=True is used with unserializable types"""
|
"""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
|
# Test with a class that has non-serializable default values
|
||||||
class ConfigWithCallable:
|
class ConfigWithCallable:
|
||||||
|
|
@ -1185,8 +1177,6 @@ def test_basemodel_reserved_names_with_json_preparsing():
|
||||||
|
|
||||||
|
|
||||||
def test_disallowed_type_qualifier():
|
def test_disallowed_type_qualifier():
|
||||||
from mcp.server.fastmcp.exceptions import InvalidSignature
|
|
||||||
|
|
||||||
def func_disallowed_qualifier() -> Final[int]: # type: ignore
|
def func_disallowed_qualifier() -> Final[int]: # type: ignore
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,10 @@ from mcp.types import (
|
||||||
NotificationParams,
|
NotificationParams,
|
||||||
ProgressNotification,
|
ProgressNotification,
|
||||||
ProgressNotificationParams,
|
ProgressNotificationParams,
|
||||||
|
PromptReference,
|
||||||
ReadResourceResult,
|
ReadResourceResult,
|
||||||
ResourceListChangedNotification,
|
ResourceListChangedNotification,
|
||||||
|
ResourceTemplateReference,
|
||||||
ServerNotification,
|
ServerNotification,
|
||||||
ServerRequest,
|
ServerRequest,
|
||||||
TextContent,
|
TextContent,
|
||||||
|
|
@ -583,8 +585,6 @@ async def test_completion(server_transport: str, server_url: str) -> None:
|
||||||
assert result.capabilities.prompts is not None
|
assert result.capabilities.prompts is not None
|
||||||
|
|
||||||
# Test resource completion
|
# Test resource completion
|
||||||
from mcp.types import ResourceTemplateReference
|
|
||||||
|
|
||||||
completion_result = await session.complete(
|
completion_result = await session.complete(
|
||||||
ref=ResourceTemplateReference(type="ref/resource", uri="github://repos/{owner}/{repo}"),
|
ref=ResourceTemplateReference(type="ref/resource", uri="github://repos/{owner}/{repo}"),
|
||||||
argument={"name": "repo", "value": ""},
|
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
|
assert "specification" in completion_result.completion.values
|
||||||
|
|
||||||
# Test prompt completion
|
# Test prompt completion
|
||||||
from mcp.types import PromptReference
|
|
||||||
|
|
||||||
completion_result = await session.complete(
|
completion_result = await session.complete(
|
||||||
ref=PromptReference(type="ref/prompt", name="review_code"),
|
ref=PromptReference(type="ref/prompt", name="review_code"),
|
||||||
argument={"name": "language", "value": "py"},
|
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"
|
assert tool_result.content[0].text == "30"
|
||||||
|
|
||||||
# Test greeting resource directly
|
# Test greeting resource directly
|
||||||
from pydantic import AnyUrl
|
|
||||||
|
|
||||||
resource_result = await session.read_resource(AnyUrl("greeting://Alice"))
|
resource_result = await session.read_resource(AnyUrl("greeting://Alice"))
|
||||||
assert len(resource_result.contents) == 1
|
assert len(resource_result.contents) == 1
|
||||||
assert isinstance(resource_result.contents[0], TextResourceContents)
|
assert isinstance(resource_result.contents[0], TextResourceContents)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from starlette.applications import Starlette
|
||||||
from starlette.routing import Mount, Route
|
from starlette.routing import Mount, Route
|
||||||
|
|
||||||
from mcp.server.fastmcp import Context, FastMCP
|
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.prompts.base import Message, UserMessage
|
||||||
from mcp.server.fastmcp.resources import FileResource, FunctionResource
|
from mcp.server.fastmcp.resources import FileResource, FunctionResource
|
||||||
from mcp.server.fastmcp.utilities.types import Audio, Image
|
from mcp.server.fastmcp.utilities.types import Audio, Image
|
||||||
|
|
@ -51,8 +53,6 @@ class TestServer:
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_sse_app_returns_starlette_app(self):
|
async def test_sse_app_returns_starlette_app(self):
|
||||||
"""Test that sse_app returns a Starlette application with correct routes."""
|
"""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
|
mcp = FastMCP("test", host="0.0.0.0") # Use 0.0.0.0 to avoid auto DNS protection
|
||||||
app = mcp.sse_app()
|
app = mcp.sse_app()
|
||||||
|
|
||||||
|
|
@ -617,8 +617,6 @@ class TestServerTools:
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_remove_nonexistent_tool(self):
|
async def test_remove_nonexistent_tool(self):
|
||||||
"""Test that removing a non-existent tool raises ToolError."""
|
"""Test that removing a non-existent tool raises ToolError."""
|
||||||
from mcp.server.fastmcp.exceptions import ToolError
|
|
||||||
|
|
||||||
mcp = FastMCP()
|
mcp = FastMCP()
|
||||||
|
|
||||||
with pytest.raises(ToolError, match="Unknown tool: nonexistent"):
|
with pytest.raises(ToolError, match="Unknown tool: nonexistent"):
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from mcp import types
|
from mcp import types
|
||||||
from mcp.client.session import ClientSession
|
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.fastmcp import Context, FastMCP
|
||||||
from mcp.server.session import ServerSession
|
from mcp.server.session import ServerSession
|
||||||
from mcp.shared.context import RequestContext
|
from mcp.shared.context import RequestContext
|
||||||
|
|
@ -110,8 +111,6 @@ async def test_url_elicitation_cancel():
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_url_elicitation_helper_function():
|
async def test_url_elicitation_helper_function():
|
||||||
"""Test the elicit_url helper function."""
|
"""Test the elicit_url helper function."""
|
||||||
from mcp.server.elicitation import elicit_url
|
|
||||||
|
|
||||||
mcp = FastMCP(name="URLElicitationHelperServer")
|
mcp = FastMCP(name="URLElicitationHelperServer")
|
||||||
|
|
||||||
@mcp.tool(description="Tool using elicit_url helper")
|
@mcp.tool(description="Tool using elicit_url helper")
|
||||||
|
|
@ -180,8 +179,6 @@ async def test_url_no_content_in_response():
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_form_mode_still_works():
|
async def test_form_mode_still_works():
|
||||||
"""Ensure form mode elicitation still works after SEP 1036."""
|
"""Ensure form mode elicitation still works after SEP 1036."""
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
mcp = FastMCP(name="FormModeBackwardCompatServer")
|
mcp = FastMCP(name="FormModeBackwardCompatServer")
|
||||||
|
|
||||||
class NameSchema(BaseModel):
|
class NameSchema(BaseModel):
|
||||||
|
|
@ -267,8 +264,6 @@ async def test_url_elicitation_required_error_code():
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_elicit_url_typed_results():
|
async def test_elicit_url_typed_results():
|
||||||
"""Test that elicit_url returns properly typed result objects."""
|
"""Test that elicit_url returns properly typed result objects."""
|
||||||
from mcp.server.elicitation import elicit_url
|
|
||||||
|
|
||||||
mcp = FastMCP(name="TypedResultsServer")
|
mcp = FastMCP(name="TypedResultsServer")
|
||||||
|
|
||||||
@mcp.tool(description="Test declined result")
|
@mcp.tool(description="Test declined result")
|
||||||
|
|
@ -329,8 +324,6 @@ async def test_elicit_url_typed_results():
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_deprecated_elicit_method():
|
async def test_deprecated_elicit_method():
|
||||||
"""Test the deprecated elicit() method for backward compatibility."""
|
"""Test the deprecated elicit() method for backward compatibility."""
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
mcp = FastMCP(name="DeprecatedElicitServer")
|
mcp = FastMCP(name="DeprecatedElicitServer")
|
||||||
|
|
||||||
class EmailSchema(BaseModel):
|
class EmailSchema(BaseModel):
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue