Get baseline 100% clean coverage (#1553)

This commit is contained in:
Max Isbey 2025-11-11 13:09:32 +00:00 • committed by GitHub
commit 89e9c43acf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 1032 additions and 1084 deletions

View file

@ -82,7 +82,7 @@ def test_get_npx_windows(monkeypatch: pytest.MonkeyPatch):
def fake_run(cmd: list[str], **kw: Any) -> subprocess.CompletedProcess[bytes]:
if cmd[0] in candidates:
return subprocess.CompletedProcess(cmd, 0)
else:
else: # pragma: no cover
raise subprocess.CalledProcessError(1, cmd[0])
monkeypatch.setattr(sys, "platform", "win32")

View file

@ -15,16 +15,16 @@ class MockTokenStorage:
self._tokens: OAuthToken | None = None
self._client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
async def get_tokens(self) -> OAuthToken | None: # pragma: no cover
return self._tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
async def set_tokens(self, tokens: OAuthToken) -> None: # pragma: no cover
self._tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
async def get_client_info(self) -> OAuthClientInformationFull | None: # pragma: no cover
return self._client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None: # pragma: no cover
self._client_info = client_info
@ -45,11 +45,11 @@ def client_metadata():
@pytest.fixture
def rfc7523_oauth_provider(client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage):
async def redirect_handler(url: str) -> None:
async def redirect_handler(url: str) -> None: # pragma: no cover
"""Mock redirect handler."""
pass
async def callback_handler() -> tuple[str, str | None]:
async def callback_handler() -> tuple[str, str | None]: # pragma: no cover
"""Mock callback handler."""
return "test_auth_code", "test_state"

View file

@ -40,7 +40,7 @@ class StreamSpyCollection:
self.client.sent_messages.clear()
self.server.sent_messages.clear()
def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]:
def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover
"""Get client-sent requests, optionally filtered by method."""
return [
req.message.root
@ -48,15 +48,15 @@ class StreamSpyCollection:
if isinstance(req.message.root, JSONRPCRequest) and (method is None or req.message.root.method == method)
]
def get_server_requests(self, method: str | None = None) -> list[JSONRPCRequest]:
def get_server_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover
"""Get server-sent requests, optionally filtered by method."""
return [
return [ # pragma: no cover
req.message.root
for req in self.server.sent_messages
if isinstance(req.message.root, JSONRPCRequest) and (method is None or req.message.root.method == method)
]
def get_client_notifications(self, method: str | None = None) -> list[JSONRPCNotification]:
def get_client_notifications(self, method: str | None = None) -> list[JSONRPCNotification]: # pragma: no cover
"""Get client-sent notifications, optionally filtered by method."""
return [
notif.message.root
@ -65,7 +65,7 @@ class StreamSpyCollection:
and (method is None or notif.message.root.method == method)
]
def get_server_notifications(self, method: str | None = None) -> list[JSONRPCNotification]:
def get_server_notifications(self, method: str | None = None) -> list[JSONRPCNotification]: # pragma: no cover
"""Get server-sent notifications, optionally filtered by method."""
return [
notif.message.root

View file

@ -22,13 +22,13 @@ class MockTokenStorage:
self._client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self._tokens
return self._tokens # pragma: no cover
async def set_tokens(self, tokens: OAuthToken) -> None:
self._tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self._client_info
return self._client_info # pragma: no cover
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self._client_info = client_info
@ -64,11 +64,11 @@ def valid_tokens():
def oauth_provider(client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage):
async def redirect_handler(url: str) -> None:
"""Mock redirect handler."""
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
"""Mock callback handler."""
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
return OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",
@ -247,10 +247,10 @@ class TestOAuthFlow:
"""Test protected resource metadata discovery URL building with fallback."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com",
@ -663,7 +663,7 @@ class TestRegistrationResponse:
@property
def text(self):
if not self._aread_called:
if not self._aread_called: # pragma: no cover
raise RuntimeError("Response.text accessed before response.aread()")
return self._text
@ -846,10 +846,10 @@ class TestAuthFlow:
# In the buggy version, this would yield the request AGAIN unconditionally
# In the fixed version, this should end the generator
try:
await auth_flow.asend(response) # extra request
request_yields += 1
# If we reach here, the bug is present
pytest.fail(
await auth_flow.asend(response) # extra request # pragma: no cover
request_yields += 1 # pragma: no cover
# If we reach here, the bug is present # pragma: no cover
pytest.fail( # pragma: no cover
f"Unnecessary retry detected! Request was yielded {request_yields} times. "
f"This indicates the retry logic bug that caused 2x performance degradation. "
f"The request should only be yielded once for successful responses."
@ -949,7 +949,7 @@ class TestAuthFlow:
success_response = httpx.Response(200, request=final_request)
try:
await auth_flow.asend(success_response)
pytest.fail("Should have stopped after successful response")
pytest.fail("Should have stopped after successful response") # pragma: no cover
except StopAsyncIteration:
pass # Expected
@ -1043,10 +1043,10 @@ class TestSEP985Discovery:
"""Test that client falls back to path-based well-known URI when WWW-Authenticate is absent."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",
@ -1076,10 +1076,10 @@ class TestSEP985Discovery:
"""Test that client falls back to root-based URI when path-based returns 404."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",
@ -1177,10 +1177,10 @@ class TestSEP985Discovery:
"""Test that WWW-Authenticate header resource_metadata takes priority over well-known URIs."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",
@ -1269,10 +1269,10 @@ class TestWWWAuthenticate:
"""Test extraction of various fields from valid WWW-Authenticate headers."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",
@ -1317,10 +1317,10 @@ class TestWWWAuthenticate:
"""Test extraction returns None for invalid cases."""
async def redirect_handler(url: str) -> None:
pass
pass # pragma: no cover
async def callback_handler() -> tuple[str, str | None]:
return "test_auth_code", "test_state"
return "test_auth_code", "test_state" # pragma: no cover
provider = OAuthClientProvider(
server_url="https://api.example.com/v1/mcp",

View file

@ -44,7 +44,7 @@ def test_command_execution(mock_config_path: Path):
test_args = [command] + args + ["--help"]
result = subprocess.run(test_args, capture_output=True, text=True, timeout=5, check=False)
result = subprocess.run(test_args, capture_output=True, text=True, timeout=20, check=False)
assert result.returncode == 0
assert "usage" in result.stdout.lower()

View file

@ -35,7 +35,7 @@ UNICODE_TEST_STRINGS = {
}
def run_unicode_server(port: int) -> None:
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
@ -167,7 +167,7 @@ def running_unicode_server(unicode_server_port: int) -> Generator[str, None, Non
# Clean up - try graceful termination first
proc.terminate()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
proc.kill()
proc.join(timeout=1)

View file

@ -19,27 +19,27 @@ async def full_featured_server():
server = FastMCP("test")
@server.tool(name="test_tool_1")
async def test_tool_1() -> str:
async def test_tool_1() -> str: # pragma: no cover
"""First test tool"""
return "Result 1"
@server.tool(name="test_tool_2")
async def test_tool_2() -> str:
async def test_tool_2() -> str: # pragma: no cover
"""Second test tool"""
return "Result 2"
@server.resource("resource://test/data")
async def test_resource() -> str:
async def test_resource() -> str: # pragma: no cover
"""Test resource"""
return "Test data"
@server.prompt()
async def test_prompt(name: str) -> str:
async def test_prompt(name: str) -> str: # pragma: no cover
"""Test prompt"""
return f"Hello, {name}!"
@server.resource("resource://test/{name}")
async def test_template(name: str) -> str:
async def test_template(name: str) -> str: # pragma: no cover
"""Test resource template"""
return f"Data for {name}"
@ -209,7 +209,7 @@ async def test_list_tools_with_strict_server_validation():
server = Server("strict_server")
@server.list_tools()
async def handle_list_tools(request: ListToolsRequest) -> ListToolsResult:
async def handle_list_tools(request: ListToolsRequest) -> ListToolsResult: # pragma: no cover
"""Strict handler that validates params field exists"""
# Simulate strict server validation

View file

@ -51,7 +51,7 @@ async def test_logging_callback():
async def message_handler(
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
async with create_session(

View file

@ -24,7 +24,7 @@ from mcp.types import ClientNotification, RootsListChangedNotification
from tests.test_helpers import wait_for_server
def create_non_sdk_server_app() -> Starlette:
def create_non_sdk_server_app() -> Starlette: # pragma: no cover
"""Create a minimal server that doesn't follow SDK conventions."""
async def handle_mcp_request(request: Request) -> Response:
@ -67,7 +67,7 @@ def create_non_sdk_server_app() -> Starlette:
return app
def run_non_sdk_server(port: int) -> None:
def run_non_sdk_server(port: int) -> None: # pragma: no cover
"""Run the non-SDK server in a separate process."""
app = create_non_sdk_server_app()
config = uvicorn.Config(
@ -95,9 +95,9 @@ def non_sdk_server(non_sdk_server_port: int) -> Generator[None, None, None]:
proc.start()
# Wait for server to be ready
try:
try: # pragma: no cover
wait_for_server(non_sdk_server_port, timeout=10.0)
except TimeoutError:
except TimeoutError: # pragma: no cover
proc.kill()
proc.join(timeout=2)
pytest.fail("Server failed to start within 10 seconds")
@ -120,7 +120,7 @@ async def test_non_compliant_notification_response(non_sdk_server: None, non_sdk
server_url = f"http://127.0.0.1:{non_sdk_server_port}/mcp"
returned_exception = None
async def message_handler(
async def message_handler( # pragma: no cover
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
):
nonlocal returned_exception
@ -141,5 +141,5 @@ async def test_non_compliant_notification_response(non_sdk_server: None, non_sdk
ClientNotification(RootsListChangedNotification(method="notifications/roots/list_changed"))
)
if returned_exception:
if returned_exception: # pragma: no cover
pytest.fail(f"Server encountered an exception: {returned_exception}")

View file

@ -19,7 +19,9 @@ async def test_send_request_stream_cleanup():
# Create a mock session with the minimal required functionality
class TestSession(BaseSession[ClientRequest, ClientNotification, ClientResult, Any, Any]):
async def _send_response(self, request_id: RequestId, response: SendResultT | ErrorData) -> None:
async def _send_response(
self, request_id: RequestId, response: SendResultT | ErrorData
) -> None: # pragma: no cover
pass
# Create streams

View file

@ -82,7 +82,7 @@ async def test_client_session_initialize():
)
# Create a message handler to catch exceptions
async def message_handler(
async def message_handler( # pragma: no cover
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, Exception):
@ -426,7 +426,7 @@ async def test_client_capabilities_with_custom_callbacks():
received_capabilities = None
async def custom_sampling_callback(
async def custom_sampling_callback( # pragma: no cover
context: RequestContext["ClientSession", Any],
params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.ErrorData:
@ -436,7 +436,7 @@ async def test_client_capabilities_with_custom_callbacks():
model="test-model",
)
async def custom_list_roots_callback(
async def custom_list_roots_callback( # pragma: no cover
context: RequestContext["ClientSession", Any],
) -> types.ListRootsResult | types.ErrorData:
return types.ListRootsResult(roots=[])

View file

@ -50,7 +50,7 @@ class TestClientSessionGroup:
mock_session = mock.AsyncMock()
# --- Prepare Session Group ---
def hook(name: str, server_info: types.Implementation) -> str:
def hook(name: str, server_info: types.Implementation) -> str: # pragma: no cover
return f"{(server_info.name)}-{name}"
mcp_session_group = ClientSessionGroup(component_name_hook=hook)
@ -344,7 +344,7 @@ class TestClientSessionGroup:
timeout=server_params_instance.timeout,
sse_read_timeout=server_params_instance.sse_read_timeout,
)
elif client_type_name == "streamablehttp":
elif client_type_name == "streamablehttp": # pragma: no branch
assert isinstance(server_params_instance, StreamableHttpParameters)
mock_specific_client_func.assert_called_once_with(
url=server_params_instance.url,

View file

@ -54,7 +54,7 @@ async def test_stdio_client():
read_messages: list[JSONRPCMessage] = []
async with read_stream:
async for message in read_stream:
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
read_messages.append(message.message)
@ -93,7 +93,7 @@ async def test_stdio_client_nonexistent_command():
# Should raise an error when trying to start the process
with pytest.raises(OSError) as exc_info:
async with stdio_client(server_params) as (_, _):
pass
pass # pragma: no cover
# The error should indicate the command was not found (ENOENT: No such file or directory)
assert exc_info.value.errno == errno.ENOENT
@ -144,7 +144,7 @@ async def test_stdio_client_universal_cleanup():
)
# Check if we timed out
if cancel_scope.cancelled_caught:
if cancel_scope.cancelled_caught: # pragma: no cover
pytest.fail(
"stdio_client cleanup timed out after 8.0 seconds. "
"This indicates the cleanup mechanism is hanging and needs fixing."
@ -153,7 +153,7 @@ async def test_stdio_client_universal_cleanup():
@pytest.mark.anyio
@pytest.mark.skipif(sys.platform == "win32", reason="Windows signal handling is different")
async def test_stdio_client_sigint_only_process():
async def test_stdio_client_sigint_only_process(): # pragma: no cover
"""
Test cleanup with a process that ignores SIGTERM but responds to SIGINT.
"""
@ -195,7 +195,7 @@ async def test_stdio_client_sigint_only_process():
# Exit context triggers cleanup - this should not hang
pass
if cancel_scope.cancelled_caught:
if cancel_scope.cancelled_caught: # pragma: no cover
raise TimeoutError("Test timed out")
end_time = time.time()
@ -208,7 +208,7 @@ async def test_stdio_client_sigint_only_process():
f"Expected < {SIGTERM_IGNORING_PROCESS_TIMEOUT} seconds. "
"This suggests the cleanup needs SIGINT/SIGKILL fallback."
)
except (TimeoutError, Exception) as e:
except (TimeoutError, Exception) as e: # pragma: no cover
if isinstance(e, TimeoutError) or "timed out" in str(e):
pytest.fail(
f"stdio_client cleanup timed out after {SIGTERM_IGNORING_PROCESS_TIMEOUT} seconds "
@ -303,7 +303,7 @@ class TestChildProcessCleanup:
assert os.path.exists(parent_marker), "Parent process didn't start"
# Verify child is writing
if os.path.exists(marker_file):
if os.path.exists(marker_file): # pragma: no branch
initial_size = os.path.getsize(marker_file)
await anyio.sleep(0.3)
size_after_wait = os.path.getsize(marker_file)
@ -318,7 +318,7 @@ class TestChildProcessCleanup:
# Verify processes stopped
await anyio.sleep(0.5)
if os.path.exists(marker_file):
if os.path.exists(marker_file): # pragma: no branch
size_after_cleanup = os.path.getsize(marker_file)
await anyio.sleep(0.5)
final_size = os.path.getsize(marker_file)
@ -335,7 +335,7 @@ class TestChildProcessCleanup:
for f in [marker_file, parent_marker]:
try:
os.unlink(f)
except OSError:
except OSError: # pragma: no cover
pass
@pytest.mark.anyio
@ -406,7 +406,7 @@ class TestChildProcessCleanup:
# Verify all are writing
for file_path, name in [(parent_file, "parent"), (child_file, "child"), (grandchild_file, "grandchild")]:
if os.path.exists(file_path):
if os.path.exists(file_path): # pragma: no branch
initial_size = os.path.getsize(file_path)
await anyio.sleep(0.3)
new_size = os.path.getsize(file_path)
@ -420,7 +420,7 @@ class TestChildProcessCleanup:
# Verify all stopped
await anyio.sleep(0.5)
for file_path, name in [(parent_file, "parent"), (child_file, "child"), (grandchild_file, "grandchild")]:
if os.path.exists(file_path):
if os.path.exists(file_path): # pragma: no branch
size1 = os.path.getsize(file_path)
await anyio.sleep(0.3)
size2 = os.path.getsize(file_path)
@ -433,7 +433,7 @@ class TestChildProcessCleanup:
for f in [parent_file, child_file, grandchild_file]:
try:
os.unlink(f)
except OSError:
except OSError: # pragma: no cover
pass
@pytest.mark.anyio
@ -487,7 +487,7 @@ class TestChildProcessCleanup:
await anyio.sleep(0.5)
# Verify child is writing
if os.path.exists(marker_file):
if os.path.exists(marker_file): # pragma: no cover
size1 = os.path.getsize(marker_file)
await anyio.sleep(0.3)
size2 = os.path.getsize(marker_file)
@ -500,7 +500,7 @@ class TestChildProcessCleanup:
# Verify child stopped
await anyio.sleep(0.5)
if os.path.exists(marker_file):
if os.path.exists(marker_file): # pragma: no branch
size3 = os.path.getsize(marker_file)
await anyio.sleep(0.3)
size4 = os.path.getsize(marker_file)
@ -512,7 +512,7 @@ class TestChildProcessCleanup:
# Clean up marker file
try:
os.unlink(marker_file)
except OSError:
except OSError: # pragma: no cover
pass
@ -560,7 +560,7 @@ async def test_stdio_client_graceful_stdin_exit():
pytest.fail(
"stdio_client cleanup timed out after 5.0 seconds. "
"Process should have exited gracefully when stdin was closed."
)
) # pragma: no cover
end_time = time.time()
elapsed = end_time - start_time
@ -619,7 +619,7 @@ async def test_stdio_client_stdin_close_ignored():
pytest.fail(
"stdio_client cleanup timed out after 7.0 seconds. "
"Process should have been terminated via SIGTERM escalation."
)
) # pragma: no cover
end_time = time.time()
elapsed = end_time - start_time

View file

@ -13,7 +13,7 @@ async def test_list_tools_returns_all_tools():
for i in range(num_tools):
@mcp.tool(name=f"tool_{i}")
def dummy_tool_func():
def dummy_tool_func(): # pragma: no cover
f"""Tool number {i}"""
return i

View file

@ -110,7 +110,7 @@ async def test_lifespan_cleanup_executed():
# Give server a moment to complete cleanup
with anyio.move_on_after(5.0):
while not Path(cleanup_marker).exists():
while not Path(cleanup_marker).exists(): # pragma: no cover
await anyio.sleep(0.1)
# Verify cleanup marker was created - this works now that stdio_client
@ -121,9 +121,9 @@ async def test_lifespan_cleanup_executed():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try:
try: # pragma: no cover
Path(path).unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass
@ -213,27 +213,27 @@ async def test_stdin_close_triggers_cleanup():
await anyio.sleep(0.1)
# Check if process is still running
if hasattr(process, "returncode") and process.returncode is not None:
if hasattr(process, "returncode") and process.returncode is not None: # pragma: no cover
pytest.fail(f"Server process exited with code {process.returncode}")
assert Path(startup_marker).exists(), "Server startup marker not created"
# Close stdin to signal shutdown
if process.stdin:
if process.stdin: # pragma: no branch
await process.stdin.aclose()
# Wait for process to exit gracefully
try:
with anyio.fail_after(5.0): # Increased from 2.0 to 5.0
await process.wait()
except TimeoutError:
except TimeoutError: # pragma: no cover
# If it doesn't exit after stdin close, terminate it
process.terminate()
await process.wait()
# Check if cleanup ran
with anyio.move_on_after(5.0):
while not Path(cleanup_marker).exists():
while not Path(cleanup_marker).exists(): # pragma: no cover
await anyio.sleep(0.1)
# Verify the cleanup ran - stdin closure enables graceful shutdown
@ -243,7 +243,7 @@ async def test_stdin_close_triggers_cleanup():
finally:
# Clean up files
for path in [server_script, startup_marker, cleanup_marker]:
try:
try: # pragma: no cover
Path(path).unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass

View file

@ -11,12 +11,12 @@ async def test_resource_templates():
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
def get_greeting(name: str) -> str: # pragma: no cover
"""Get a personalized greeting"""
return f"Hello, {name}!"
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
def get_user_profile(user_id: str) -> str: # pragma: no cover
"""Dynamic user data"""
return f"Profile data for user {user_id}"
@ -33,10 +33,10 @@ async def test_resource_templates():
assert len(templates) == 2
# Verify template details
greeting_template = next(t for t in templates if t.name == "get_greeting")
greeting_template = next(t for t in templates if t.name == "get_greeting") # pragma: no cover
assert greeting_template.uriTemplate == "greeting://{name}"
assert greeting_template.description == "Get a personalized greeting"
profile_template = next(t for t in templates if t.name == "get_user_profile")
profile_template = next(t for t in templates if t.name == "get_user_profile") # pragma: no cover
assert profile_template.uriTemplate == "users://{user_id}/profile"
assert profile_template.description == "Dynamic user data"

View file

@ -23,25 +23,25 @@ async def test_icons_and_website_url():
# Create tool with icon
@mcp.tool(icons=[test_icon])
def test_tool(message: str) -> str:
def test_tool(message: str) -> str: # pragma: no cover
"""A test tool with an icon."""
return message
# Create resource with icon
@mcp.resource("test://resource", icons=[test_icon])
def test_resource() -> str:
def test_resource() -> str: # pragma: no cover
"""A test resource with an icon."""
return "test content"
# Create prompt with icon
@mcp.prompt("test_prompt", icons=[test_icon])
def test_prompt(text: str) -> str:
def test_prompt(text: str) -> str: # pragma: no cover
"""A test prompt with an icon."""
return text
# Create resource template with icon
@mcp.resource("test://weather/{city}", icons=[test_icon])
def test_resource_template(city: str) -> str:
def test_resource_template(city: str) -> str: # pragma: no cover
"""Get weather for a city."""
return f"Weather for {city}"
@ -104,7 +104,7 @@ async def test_multiple_icons():
# Create tool with multiple icons
@mcp.tool(icons=[icon1, icon2, icon3])
def multi_icon_tool() -> str:
def multi_icon_tool() -> str: # pragma: no cover
"""A tool with multiple icons."""
return "success"
@ -125,7 +125,7 @@ async def test_no_icons_or_website():
mcp = FastMCP("BasicServer")
@mcp.tool()
def basic_tool() -> str:
def basic_tool() -> str: # pragma: no cover
"""A basic tool without icons."""
return "success"

View file

@ -25,28 +25,28 @@ async def test_resource_template_edge_cases():
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile(user_id: str, optional_param: str | None = None) -> str:
def get_user_profile(user_id: str, optional_param: str | None = None) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Test case 3: Template with mismatched parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile_mismatch(different_param: str) -> str:
def get_user_profile_mismatch(different_param: str) -> str: # pragma: no cover
return f"Profile for user {different_param}"
# Test case 4: Template with extra function parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile")
def get_user_profile_extra(user_id: str, extra_param: str) -> str:
def get_user_profile_extra(user_id: str, extra_param: str) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Test case 5: Template with missing function parameters
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://users/{user_id}/profile/{section}")
def get_user_profile_missing(user_id: str) -> str:
def get_user_profile_missing(user_id: str) -> str: # pragma: no cover
return f"Profile for user {user_id}"
# Verify valid template works

View file

@ -88,7 +88,7 @@ async def test_lowlevel_resource_mime_type():
return [ReadResourceContents(content=base64_string, mime_type="image/png")]
elif str(uri) == "test://image_bytes":
return [ReadResourceContents(content=bytes(image_bytes), mime_type="image/png")]
raise Exception(f"Resource not found: {uri}")
raise Exception(f"Resource not found: {uri}") # pragma: no cover
# Test that resources are listed with correct mime type
async with client_session(server) as client:

View file

@ -8,13 +8,13 @@ from mcp.server.session import ServerSession
class Database: # Replace with your actual DB type
@classmethod
async def connect(cls):
async def connect(cls): # pragma: no cover
return cls()
async def disconnect(self):
async def disconnect(self): # pragma: no cover
pass
def query(self):
def query(self): # pragma: no cover
return "Hello, World!"
@ -28,7 +28,7 @@ class AppContext:
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: # pragma: no cover
"""Manage application lifecycle with type-safe context"""
# Initialize on startup
db = await Database.connect()
@ -45,7 +45,7 @@ mcp = FastMCP("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools
@mcp.tool()
def query_db(ctx: Context[ServerSession, AppContext]) -> str:
def query_db(ctx: Context[ServerSession, AppContext]) -> str: # pragma: no cover
"""Tool that uses initialized resources"""
db = ctx.request_context.lifespan_context.db
return db.query()

Some files were not shown because too many files have changed in this diff Show more