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
31 changed files with 1032 additions and 1084 deletions

View file

@ -61,10 +61,10 @@ class TestAuthContextMiddleware:
scope: Scope = {"type": "http", "user": user}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
async def send(message: Message) -> None:
async def send(message: Message) -> None: # pragma: no cover
pass
# Verify context is empty before middleware
@ -95,10 +95,10 @@ class TestAuthContextMiddleware:
scope: Scope = {"type": "http"} # No user
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
async def send(message: Message) -> None:
async def send(message: Message) -> None: # pragma: no cover
pass
# Verify context is empty before middleware

View file

@ -276,7 +276,7 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http"}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
sent_messages: list[Message] = []
@ -300,7 +300,7 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": object()}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
sent_messages: list[Message] = []
@ -329,7 +329,7 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": user, "auth": auth}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
sent_messages: list[Message] = []
@ -357,7 +357,7 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": user} # No auth credentials
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
sent_messages: list[Message] = []
@ -386,10 +386,10 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": user, "auth": auth}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
async def send(message: Message) -> None:
async def send(message: Message) -> None: # pragma: no cover
pass
await middleware(scope, receive, send)
@ -411,10 +411,10 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": user, "auth": auth}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
async def send(message: Message) -> None:
async def send(message: Message) -> None: # pragma: no cover
pass
await middleware(scope, receive, send)
@ -436,10 +436,10 @@ class TestRequireAuthMiddleware:
scope: Scope = {"type": "http", "user": user, "auth": auth}
# Create dummy async functions for receive and send
async def receive() -> Message:
async def receive() -> Message: # pragma: no cover
return {"type": "http.request"}
async def send(message: Message) -> None:
async def send(message: Message) -> None: # pragma: no cover
pass
await middleware(scope, receive, send)

View file

@ -100,7 +100,7 @@ class MockOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, Refr
if old_access_token is None:
return None
token_info = self.tokens.get(old_access_token)
if token_info is None:
if token_info is None: # pragma: no cover
return None
# Create a RefreshToken object that matches what is expected in later code
@ -174,17 +174,17 @@ class MockOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, Refr
async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
match token:
case RefreshToken():
case RefreshToken(): # pragma: no cover
# Remove the refresh token
del self.refresh_tokens[token.token]
case AccessToken():
case AccessToken(): # pragma: no branch
# Remove the access token
del self.tokens[token.token]
# Also remove any refresh tokens that point to this access token
for refresh_token, access_token in list(self.refresh_tokens.items()):
if access_token == token.token:
if access_token == token.token: # pragma: no branch
del self.refresh_tokens[refresh_token]
@ -283,7 +283,7 @@ async def auth_code(
}
# Override with any parameters from the test
if hasattr(request, "param") and request.param:
if hasattr(request, "param") and request.param: # pragma: no cover
auth_params.update(request.param)
response = await test_client.get("/authorize", params=auth_params)
@ -304,44 +304,6 @@ async def auth_code(
}
@pytest.fixture
async def tokens(
test_client: httpx.AsyncClient,
registered_client: dict[str, Any],
auth_code: dict[str, str],
pkce_challenge: dict[str, str],
request: pytest.FixtureRequest,
):
"""Exchange authorization code for tokens.
Parameters can be customized via indirect parameterization:
@pytest.mark.parametrize("tokens",
[{"code_verifier": "wrong_verifier"}],
indirect=True)
"""
# Default token request params
token_params = {
"grant_type": "authorization_code",
"client_id": registered_client["client_id"],
"client_secret": registered_client["client_secret"],
"code": auth_code["code"],
"code_verifier": pkce_challenge["code_verifier"],
"redirect_uri": auth_code["redirect_uri"],
}
# Override with any parameters from the test
if hasattr(request, "param") and request.param:
token_params.update(request.param)
response = await test_client.post("/token", data=token_params)
# Don't assert success here since some tests will intentionally cause errors
return {
"response": response,
"params": token_params,
}
class TestAuthEndpoints:
@pytest.mark.anyio
async def test_metadata_endpoint(self, test_client: httpx.AsyncClient):
@ -422,8 +384,8 @@ class TestAuthEndpoints:
# Find the auth code object
code_value = auth_code["code"]
found_code = None
for code_obj in mock_oauth_provider.auth_codes.values():
if code_obj.code == code_value:
for code_obj in mock_oauth_provider.auth_codes.values(): # pragma: no branch
if code_obj.code == code_value: # pragma: no branch
found_code = code_obj
break

View file

@ -36,7 +36,7 @@ class TestRenderPrompt:
@pytest.mark.anyio
async def test_fn_with_invalid_kwargs(self):
async def fn(name: str, age: int = 30) -> str:
async def fn(name: str, age: int = 30) -> str: # pragma: no cover
return f"Hello, {name}! You're {age} years old."
prompt = Prompt.from_function(fn)

View file

@ -8,7 +8,7 @@ class TestPromptManager:
def test_add_prompt(self):
"""Test adding a prompt to the manager."""
def fn() -> str:
def fn() -> str: # pragma: no cover
return "Hello, world!"
manager = PromptManager()
@ -20,7 +20,7 @@ class TestPromptManager:
def test_add_duplicate_prompt(self, caplog: pytest.LogCaptureFixture):
"""Test adding the same prompt twice."""
def fn() -> str:
def fn() -> str: # pragma: no cover
return "Hello, world!"
manager = PromptManager()
@ -33,7 +33,7 @@ class TestPromptManager:
def test_disable_warn_on_duplicate_prompts(self, caplog: pytest.LogCaptureFixture):
"""Test disabling warning on duplicate prompts."""
def fn() -> str:
def fn() -> str: # pragma: no cover
return "Hello, world!"
manager = PromptManager(warn_on_duplicate_prompts=False)
@ -46,10 +46,10 @@ class TestPromptManager:
def test_list_prompts(self):
"""Test listing all prompts."""
def fn1() -> str:
def fn1() -> str: # pragma: no cover
return "Hello, world!"
def fn2() -> str:
def fn2() -> str: # pragma: no cover
return "Goodbye, world!"
manager = PromptManager()
@ -98,7 +98,7 @@ class TestPromptManager:
async def test_render_prompt_with_missing_args(self):
"""Test rendering a prompt with missing required arguments."""
def fn(name: str) -> str:
def fn(name: str) -> str: # pragma: no cover
return f"Hello, {name}!"
manager = PromptManager()

View file

@ -19,9 +19,9 @@ def temp_file():
f.write(content)
path = Path(f.name).resolve()
yield path
try:
try: # pragma: no cover
path.unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass # File was already deleted by the test
@ -102,7 +102,7 @@ class TestFileResource:
@pytest.mark.skipif(os.name == "nt", reason="File permissions behave differently on Windows")
@pytest.mark.anyio
async def test_permission_error(self, temp_file: Path):
async def test_permission_error(self, temp_file: Path): # pragma: no cover
"""Test reading a file without permissions."""
temp_file.chmod(0o000) # Remove all permissions
try:

View file

@ -10,7 +10,7 @@ class TestFunctionResource:
def test_function_resource_creation(self):
"""Test creating a FunctionResource."""
def my_func() -> str:
def my_func() -> str: # pragma: no cover
return "test content"
resource = FunctionResource(
@ -141,7 +141,7 @@ class TestFunctionResource:
async def test_from_function(self):
"""Test creating a FunctionResource from a function."""
async def get_data() -> str:
async def get_data() -> str: # pragma: no cover
"""get_data returns a string"""
return "Hello, world!"

View file

@ -18,9 +18,9 @@ def temp_file():
f.write(content)
path = Path(f.name).resolve()
yield path
try:
try: # pragma: no cover
path.unlink()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
pass # File was already deleted by the test

View file

@ -15,7 +15,7 @@ class TestResourceTemplate:
def test_template_creation(self):
"""Test creating a template from a function."""
def my_func(key: str, value: int) -> dict[str, Any]:
def my_func(key: str, value: int) -> dict[str, Any]: # pragma: no cover
return {"key": key, "value": value}
template = ResourceTemplate.from_function(
@ -31,7 +31,7 @@ class TestResourceTemplate:
def test_template_matches(self):
"""Test matching URIs against a template."""
def my_func(key: str, value: int) -> dict[str, Any]:
def my_func(key: str, value: int) -> dict[str, Any]: # pragma: no cover
return {"key": key, "value": value}
template = ResourceTemplate.from_function(
@ -196,7 +196,7 @@ class TestResourceTemplateAnnotations:
def test_template_with_annotations(self):
"""Test creating a template with annotations."""
def get_user_data(user_id: str) -> str:
def get_user_data(user_id: str) -> str: # pragma: no cover
return f"User {user_id}"
annotations = Annotations(priority=0.9)
@ -211,7 +211,7 @@ class TestResourceTemplateAnnotations:
def test_template_without_annotations(self):
"""Test that annotations are optional for templates."""
def get_user_data(user_id: str) -> str:
def get_user_data(user_id: str) -> str: # pragma: no cover
return f"User {user_id}"
template = ResourceTemplate.from_function(fn=get_user_data, uri_template="resource://users/{user_id}")
@ -225,7 +225,7 @@ class TestResourceTemplateAnnotations:
mcp = FastMCP()
@mcp.resource("resource://dynamic/{id}", annotations=Annotations(audience=["user"], priority=0.7))
def get_dynamic(id: str) -> str:
def get_dynamic(id: str) -> str: # pragma: no cover
"""A dynamic annotated resource."""
return f"Data for {id}"
@ -239,7 +239,7 @@ class TestResourceTemplateAnnotations:
async def test_template_created_resources_inherit_annotations(self):
"""Test that resources created from templates inherit annotations."""
def get_item(item_id: str) -> str:
def get_item(item_id: str) -> str: # pragma: no cover
return f"Item {item_id}"
annotations = Annotations(priority=0.6)

View file

@ -12,7 +12,7 @@ class TestResourceValidation:
def test_resource_uri_validation(self):
"""Test URI validation."""
def dummy_func() -> str:
def dummy_func() -> str: # pragma: no cover
return "data"
# Valid URI
@ -42,7 +42,7 @@ class TestResourceValidation:
def test_resource_name_from_uri(self):
"""Test name is extracted from URI if not provided."""
def dummy_func() -> str:
def dummy_func() -> str: # pragma: no cover
return "data"
resource = FunctionResource(
@ -54,7 +54,7 @@ class TestResourceValidation:
def test_resource_name_validation(self):
"""Test name validation."""
def dummy_func() -> str:
def dummy_func() -> str: # pragma: no cover
return "data"
# Must provide either name or URI
@ -74,7 +74,7 @@ class TestResourceValidation:
def test_resource_mime_type(self):
"""Test mime type handling."""
def dummy_func() -> str:
def dummy_func() -> str: # pragma: no cover
return "data"
# Default mime type
@ -109,7 +109,7 @@ class TestResourceAnnotations:
def test_resource_with_annotations(self):
"""Test creating a resource with annotations."""
def get_data() -> str:
def get_data() -> str: # pragma: no cover
return "data"
annotations = Annotations(audience=["user"], priority=0.8)
@ -123,7 +123,7 @@ class TestResourceAnnotations:
def test_resource_without_annotations(self):
"""Test that annotations are optional."""
def get_data() -> str:
def get_data() -> str: # pragma: no cover
return "data"
resource = FunctionResource.from_function(fn=get_data, uri="resource://test")
@ -137,7 +137,7 @@ class TestResourceAnnotations:
mcp = FastMCP()
@mcp.resource("resource://annotated", annotations=Annotations(audience=["assistant"], priority=0.5))
def get_annotated() -> str:
def get_annotated() -> str: # pragma: no cover
"""An annotated resource."""
return "annotated data"
@ -154,7 +154,7 @@ class TestResourceAnnotations:
mcp = FastMCP()
@mcp.resource("resource://both", annotations=Annotations(audience=["user", "assistant"], priority=1.0))
def get_both() -> str:
def get_both() -> str: # pragma: no cover
return "for everyone"
resources = await mcp.list_resources()

View file

@ -44,17 +44,17 @@ def resources(mcp: FastMCP, test_dir: Path) -> FastMCP:
@mcp.resource("file://test_dir/readme.md")
def read_readme_md() -> str:
"""Read the readme.md file"""
try:
try: # pragma: no cover
return (test_dir / "readme.md").read_text()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
return "File not found"
@mcp.resource("file://test_dir/config.json")
def read_config_json() -> str:
"""Read the config.json file"""
try:
try: # pragma: no cover
return (test_dir / "config.json").read_text()
except FileNotFoundError:
except FileNotFoundError: # pragma: no cover
return "File not found"
return mcp
@ -65,7 +65,7 @@ def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
@mcp.tool()
def delete_file(path: str) -> bool:
# ensure path is in test_dir
if Path(path).resolve().parent != test_dir:
if Path(path).resolve().parent != test_dir: # pragma: no cover
raise ValueError(f"Path must be in test_dir: {path}")
Path(path).unlink()
return True

View file

@ -31,7 +31,7 @@ def create_ask_user_tool(mcp: FastMCP):
return f"User answered: {result.data.answer}"
elif result.action == "decline":
return "User declined to answer"
else:
else: # pragma: no cover
return "User cancelled"
return ask_user
@ -57,7 +57,7 @@ async def call_tool_and_assert(
if expected_text is not None:
assert result.content[0].text == expected_text
elif text_contains is not None:
elif text_contains is not None: # pragma: no branch
for substring in text_contains:
assert substring in result.content[0].text
@ -71,7 +71,9 @@ async def test_stdio_elicitation():
create_ask_user_tool(mcp)
# Create a custom handler for elicitation requests
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
async def elicitation_callback(
context: RequestContext[ClientSession, None], params: ElicitRequestParams
): # pragma: no cover
if params.message == "Tool wants to ask: What is your name?":
return ElicitResult(action="accept", content={"answer": "Test User"})
else:
@ -103,7 +105,7 @@ async def test_elicitation_schema_validation():
def create_validation_tool(name: str, schema_class: type[BaseModel]):
@mcp.tool(name=name, description=f"Tool testing {name}")
async def tool(ctx: Context[ServerSession, None]) -> str:
async def tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover
try:
await ctx.elicit(message="This should fail validation", schema=schema_class)
return "Should not reach here"
@ -126,7 +128,9 @@ async def test_elicitation_schema_validation():
create_validation_tool("nested_model", InvalidNestedSchema)
# Dummy callback (won't be called due to validation failure)
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
async def elicitation_callback(
context: RequestContext[ClientSession, None], params: ElicitRequestParams
): # pragma: no cover
return ElicitResult(action="accept", content={})
async with create_connected_server_and_client_session(
@ -166,7 +170,7 @@ async def test_elicitation_with_optional_fields():
info.append(f"Email: {result.data.optional_email}")
info.append(f"Subscribe: {result.data.subscribe}")
return ", ".join(info)
else:
else: # pragma: no cover
return f"User {result.action}"
# Test cases with different field combinations
@ -196,14 +200,16 @@ async def test_elicitation_with_optional_fields():
optional_list: list[str] | None = Field(default=None, description="Invalid optional list")
@mcp.tool(description="Tool with invalid optional field")
async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str:
async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover
try:
await ctx.elicit(message="This should fail", schema=InvalidOptionalSchema)
return "Should not reach here"
except TypeError as e:
return f"Validation failed: {str(e)}"
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
async def elicitation_callback(
context: RequestContext[ClientSession, None], params: ElicitRequestParams
): # pragma: no cover
return ElicitResult(action="accept", content={})
await call_tool_and_assert(
@ -235,7 +241,7 @@ async def test_elicitation_with_default_values():
f"Name: {result.data.name}, Age: {result.data.age}, "
f"Subscribe: {result.data.subscribe}, Email: {result.data.email}"
)
else:
else: # pragma: no cover
return f"User {result.action}"
# First verify that defaults are present in the JSON schema sent to clients

View file

@ -161,7 +161,7 @@ def test_str_vs_list_str():
We want to make sure it's kept as a python string.
"""
def func_with_str_types(str_or_list: str | list[str]):
def func_with_str_types(str_or_list: str | list[str]): # pragma: no cover
return str_or_list
meta = func_metadata(func_with_str_types)
@ -184,7 +184,7 @@ def test_str_vs_list_str():
def test_skip_names():
"""Test that skipped parameters are not included in the model"""
def func_with_many_params(keep_this: int, skip_this: str, also_keep: float, also_skip: bool):
def func_with_many_params(keep_this: int, skip_this: str, also_keep: float, also_skip: bool): # pragma: no cover
return keep_this, skip_this, also_keep, also_skip
# Skip some parameters
@ -206,7 +206,7 @@ def test_structured_output_dict_str_types():
"""Test that dict[str, T] types are handled without wrapping."""
# Test dict[str, Any]
def func_dict_any() -> dict[str, Any]:
def func_dict_any() -> dict[str, Any]: # pragma: no cover
return {"a": 1, "b": "hello", "c": [1, 2, 3]}
meta = func_metadata(func_dict_any)
@ -214,7 +214,7 @@ def test_structured_output_dict_str_types():
assert meta.output_schema == IsPartialDict(type="object", title="func_dict_anyDictOutput")
# Test dict[str, str]
def func_dict_str() -> dict[str, str]:
def func_dict_str() -> dict[str, str]: # pragma: no cover
return {"name": "John", "city": "NYC"}
meta = func_metadata(func_dict_str)
@ -225,7 +225,7 @@ def test_structured_output_dict_str_types():
}
# Test dict[str, list[int]]
def func_dict_list() -> dict[str, list[int]]:
def func_dict_list() -> dict[str, list[int]]: # pragma: no cover
return {"nums": [1, 2, 3], "more": [4, 5, 6]}
meta = func_metadata(func_dict_list)
@ -236,7 +236,7 @@ def test_structured_output_dict_str_types():
}
# Test dict[int, str] - should be wrapped since key is not str
def func_dict_int_key() -> dict[int, str]:
def func_dict_int_key() -> dict[int, str]: # pragma: no cover
return {1: "a", 2: "b"}
meta = func_metadata(func_dict_int_key)
@ -312,8 +312,8 @@ def test_complex_function_json_schema():
normalized_schema = actual_schema.copy()
# Normalize the my_model_a_with_default field to handle both pydantic formats
if "allOf" in actual_schema["properties"]["my_model_a_with_default"]:
normalized_schema["properties"]["my_model_a_with_default"] = {
if "allOf" in actual_schema["properties"]["my_model_a_with_default"]: # pragma: no cover
normalized_schema["properties"]["my_model_a_with_default"] = { # pragma: no cover
"$ref": "#/$defs/SomeInputModelA",
"default": {},
}
@ -452,7 +452,7 @@ def test_str_vs_int():
while numbers are parsed correctly.
"""
def func_with_str_and_int(a: str, b: int):
def func_with_str_and_int(a: str, b: int): # pragma: no cover
return a
meta = func_metadata(func_with_str_and_int)
@ -470,7 +470,7 @@ def test_str_annotation_preserves_json_string():
and passes after the fix (JSON string remains as string).
"""
def process_json_config(config: str, enabled: bool = True) -> str:
def process_json_config(config: str, enabled: bool = True) -> str: # pragma: no cover
"""Function that expects a JSON string as a string parameter."""
# In real use, this function might validate or transform the JSON string
# before parsing it, or pass it to another service as-is
@ -518,7 +518,7 @@ async def test_str_annotation_runtime_validation():
containing valid JSON to ensure they are passed as strings, not parsed objects.
"""
def handle_json_payload(payload: str, strict_mode: bool = False) -> str:
def handle_json_payload(payload: str, strict_mode: bool = False) -> str: # pragma: no cover
"""Function that processes a JSON payload as a string."""
# This function expects to receive the raw JSON string
# It might parse it later after validation or logging
@ -560,10 +560,10 @@ 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():
def func_no_annotation(): # pragma: no cover
return "hello"
def func_none_annotation() -> None:
def func_none_annotation() -> None: # pragma: no cover
return None
with pytest.raises(InvalidSignature) as exc_info:
@ -588,7 +588,7 @@ def test_structured_output_basemodel():
age: int
email: str | None = None
def func_returning_person() -> PersonModel:
def func_returning_person() -> PersonModel: # pragma: no cover
return PersonModel(name="Alice", age=30)
meta = func_metadata(func_returning_person)
@ -607,19 +607,19 @@ def test_structured_output_basemodel():
def test_structured_output_primitives():
"""Test structured output with primitive return types"""
def func_str() -> str:
def func_str() -> str: # pragma: no cover
return "hello"
def func_int() -> int:
def func_int() -> int: # pragma: no cover
return 42
def func_float() -> float:
def func_float() -> float: # pragma: no cover
return 3.14
def func_bool() -> bool:
def func_bool() -> bool: # pragma: no cover
return True
def func_bytes() -> bytes:
def func_bytes() -> bytes: # pragma: no cover
return b"data"
# Test string
@ -671,16 +671,16 @@ def test_structured_output_primitives():
def test_structured_output_generic_types():
"""Test structured output with generic types (list, dict, Union, etc.)"""
def func_list_str() -> list[str]:
def func_list_str() -> list[str]: # pragma: no cover
return ["a", "b", "c"]
def func_dict_str_int() -> dict[str, int]:
def func_dict_str_int() -> dict[str, int]: # pragma: no cover
return {"a": 1, "b": 2}
def func_union() -> str | int:
def func_union() -> str | int: # pragma: no cover
return "hello"
def func_optional() -> str | None:
def func_optional() -> str | None: # pragma: no cover
return None
# Test list
@ -729,7 +729,7 @@ def test_structured_output_dataclass():
email: str | None = None
tags: list[str] | None = None
def func_returning_dataclass() -> PersonDataClass:
def func_returning_dataclass() -> PersonDataClass: # pragma: no cover
return PersonDataClass(name="Bob", age=25)
meta = func_metadata(func_returning_dataclass)
@ -757,7 +757,7 @@ def test_structured_output_typeddict():
name: str
age: int
def func_returning_typeddict_optional() -> PersonTypedDictOptional:
def func_returning_typeddict_optional() -> PersonTypedDictOptional: # pragma: no cover
return {"name": "Dave"} # Only returning one field to test partial dict
meta = func_metadata(func_returning_typeddict_optional)
@ -776,7 +776,7 @@ def test_structured_output_typeddict():
age: int
email: str | None
def func_returning_typeddict_required() -> PersonTypedDictRequired:
def func_returning_typeddict_required() -> PersonTypedDictRequired: # pragma: no cover
return {"name": "Eve", "age": 40, "email": None} # Testing None value
meta = func_metadata(func_returning_typeddict_required)
@ -800,12 +800,12 @@ def test_structured_output_ordinary_class():
age: int
email: str | None
def __init__(self, name: str, age: int, email: str | None = None):
def __init__(self, name: str, age: int, email: str | None = None): # pragma: no cover
self.name = name
self.age = age
self.email = email
def func_returning_class() -> PersonClass:
def func_returning_class() -> PersonClass: # pragma: no cover
return PersonClass("Helen", 55)
meta = func_metadata(func_returning_class)
@ -824,11 +824,11 @@ def test_structured_output_ordinary_class():
def test_unstructured_output_unannotated_class():
# Test with class that has no annotations
class UnannotatedClass:
def __init__(self, x, y):
def __init__(self, x, y): # pragma: no cover
self.x = x
self.y = y
def func_returning_unannotated() -> UnannotatedClass:
def func_returning_unannotated() -> UnannotatedClass: # pragma: no cover
return UnannotatedClass(1, 2)
meta = func_metadata(func_returning_unannotated)
@ -836,7 +836,7 @@ def test_unstructured_output_unannotated_class():
def test_tool_call_result_is_unstructured_and_not_converted():
def func_returning_call_tool_result() -> CallToolResult:
def func_returning_call_tool_result() -> CallToolResult: # pragma: no cover
return CallToolResult(content=[])
meta = func_metadata(func_returning_call_tool_result)
@ -849,7 +849,7 @@ def test_tool_call_result_annotated_is_structured_and_converted():
class PersonClass(BaseModel):
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]:
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
return CallToolResult(content=[], structuredContent={"name": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
@ -869,7 +869,7 @@ def test_tool_call_result_annotated_is_structured_and_invalid():
class PersonClass(BaseModel):
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]:
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
return CallToolResult(content=[], structuredContent={"person": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
@ -883,7 +883,7 @@ def test_tool_call_result_in_optional_is_rejected():
from mcp.server.fastmcp.exceptions import InvalidSignature
def func_optional_call_tool_result() -> CallToolResult | None:
def func_optional_call_tool_result() -> CallToolResult | None: # pragma: no cover
return CallToolResult(content=[])
with pytest.raises(InvalidSignature) as exc_info:
@ -898,7 +898,7 @@ def test_tool_call_result_in_union_is_rejected():
from mcp.server.fastmcp.exceptions import InvalidSignature
def func_union_call_tool_result() -> str | CallToolResult:
def func_union_call_tool_result() -> str | CallToolResult: # pragma: no cover
return CallToolResult(content=[])
with pytest.raises(InvalidSignature) as exc_info:
@ -912,7 +912,7 @@ 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:
def func_pipe_union_call_tool_result() -> str | CallToolResult: # pragma: no cover
return CallToolResult(content=[])
with pytest.raises(InvalidSignature) as exc_info:
@ -929,7 +929,7 @@ def test_structured_output_with_field_descriptions():
name: Annotated[str, Field(description="The person's full name")]
age: Annotated[int, Field(description="Age in years", ge=0, le=150)]
def func_with_descriptions() -> ModelWithDescriptions:
def func_with_descriptions() -> ModelWithDescriptions: # pragma: no cover
return ModelWithDescriptions(name="Ian", age=60)
meta = func_metadata(func_with_descriptions)
@ -956,7 +956,7 @@ def test_structured_output_nested_models():
name: str
address: Address
def func_nested() -> PersonWithAddress:
def func_nested() -> PersonWithAddress: # pragma: no cover
return PersonWithAddress(name="Jack", address=Address(street="123 Main St", city="Anytown", zipcode="12345"))
meta = func_metadata(func_nested)
@ -995,7 +995,7 @@ def test_structured_output_unserializable_type_error():
# Callable defaults are not JSON serializable and will trigger Pydantic warnings
callback: Callable[[Any], Any] = lambda x: x * 2
def func_returning_config_with_callable() -> ConfigWithCallable:
def func_returning_config_with_callable() -> ConfigWithCallable: # pragma: no cover
return ConfigWithCallable()
# Should work without structured_output=True (returns None for output_schema)
@ -1013,7 +1013,7 @@ def test_structured_output_unserializable_type_error():
x: int
y: int
def func_returning_namedtuple() -> Point:
def func_returning_namedtuple() -> Point: # pragma: no cover
return Point(1, 2)
# Should work without structured_output=True (returns None for output_schema)
@ -1034,7 +1034,7 @@ def test_structured_output_aliases():
field_first: str | None = Field(default=None, alias="first", description="The first field.")
field_second: str | None = Field(default=None, alias="second", description="The second field.")
def func_with_aliases() -> ModelWithAliases:
def func_with_aliases() -> ModelWithAliases: # pragma: no cover
# When aliases are defined, we must use the aliased names to set values
return ModelWithAliases(**{"first": "hello", "second": "world"})
@ -1075,7 +1075,7 @@ def test_structured_output_aliases():
def test_basemodel_reserved_names():
"""Test that functions with parameters named after BaseModel methods work correctly"""
def func_with_reserved_names(
def func_with_reserved_names( # pragma: no cover
model_dump: str,
model_validate: int,
dict: list[str],
@ -1103,7 +1103,7 @@ def test_basemodel_reserved_names():
async def test_basemodel_reserved_names_validation():
"""Test that validation and calling works with reserved parameter names"""
def func_with_reserved_names(
def func_with_reserved_names( # pragma: no cover
model_dump: str,
model_validate: int,
dict: list[str],
@ -1161,7 +1161,7 @@ async def test_basemodel_reserved_names_validation():
def test_basemodel_reserved_names_with_json_preparsing():
"""Test that pre_parse_json works correctly with reserved parameter names"""
def func_with_reserved_json(
def func_with_reserved_json( # pragma: no cover
json: dict[str, Any],
model_dump: list[int],
normal: str,

View file

@ -75,14 +75,14 @@ class NotificationCollector:
self, message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception
) -> None:
"""Handle any server notification and route to appropriate handler."""
if isinstance(message, ServerNotification):
if isinstance(message, ServerNotification): # pragma: no branch
if isinstance(message.root, ProgressNotification):
self.progress_notifications.append(message.root.params)
elif isinstance(message.root, LoggingMessageNotification):
self.log_messages.append(message.root.params)
elif isinstance(message.root, ResourceListChangedNotification):
self.resource_notifications.append(message.root.params)
elif isinstance(message.root, ToolListChangedNotification):
elif isinstance(message.root, ToolListChangedNotification): # pragma: no cover
self.tool_notifications.append(message.root.params)
@ -101,7 +101,7 @@ def server_url(server_port: int) -> str:
return f"http://127.0.0.1:{server_port}"
def run_server_with_transport(module_name: str, port: int, transport: str) -> None:
def run_server_with_transport(module_name: str, port: int, transport: str) -> None: # pragma: no cover
"""Run server with specified transport."""
# Get the MCP instance based on module name
if module_name == "basic_tool":
@ -167,7 +167,7 @@ def server_transport(request: pytest.FixtureRequest, server_port: int) -> Genera
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("Server process failed to terminate")
@ -180,7 +180,7 @@ def create_client_for_transport(transport: str, server_url: str):
elif transport == "streamable-http":
endpoint = f"{server_url}/mcp"
return streamablehttp_client(endpoint)
else:
else: # pragma: no cover
raise ValueError(f"Invalid transport: {transport}")
@ -233,7 +233,7 @@ async def elicitation_callback(context: RequestContext[ClientSession, None], par
action="accept",
content={"checkAlternative": True, "alternativeDate": "2024-12-26"},
)
else:
else: # pragma: no cover
return ElicitResult(action="decline")
@ -385,7 +385,7 @@ async def test_tool_progress(server_transport: str, server_url: str) -> None:
async def message_handler(message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception):
await collector.handle_generic_notification(message)
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
client_cm = create_client_for_transport(transport, server_url)
@ -526,7 +526,7 @@ async def test_notifications(server_transport: str, server_url: str) -> None:
async def message_handler(message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception):
await collector.handle_generic_notification(message)
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
client_cm = create_client_for_transport(transport, server_url)

View file

@ -14,7 +14,7 @@ async def test_parameter_descriptions():
def greet(
name: str = Field(description="The name to greet"),
title: str = Field(description="Optional title", default=""),
) -> str:
) -> str: # pragma: no cover
"""A greeting tool"""
return f"Hello {title} {name}"

View file

@ -147,7 +147,7 @@ class TestServer:
mcp = FastMCP()
@mcp.tool()
def sum(x: int, y: int) -> int:
def sum(x: int, y: int) -> int: # pragma: no cover
return x + y
assert len(mcp._tool_manager.list_tools()) == 1
@ -159,7 +159,7 @@ class TestServer:
with pytest.raises(TypeError, match="The @tool decorator was used incorrectly"):
@mcp.tool # Missing parentheses #type: ignore
def sum(x: int, y: int) -> int:
def sum(x: int, y: int) -> int: # pragma: no cover
return x + y
@pytest.mark.anyio
@ -167,7 +167,7 @@ class TestServer:
mcp = FastMCP()
@mcp.resource("r://{x}")
def get_data(x: str) -> str:
def get_data(x: str) -> str: # pragma: no cover
return f"Data: {x}"
assert len(mcp._resource_manager._templates) == 1
@ -179,7 +179,7 @@ class TestServer:
with pytest.raises(TypeError, match="The @resource decorator was used incorrectly"):
@mcp.resource # Missing parentheses #type: ignore
def get_data(x: str) -> str:
def get_data(x: str) -> str: # pragma: no cover
return f"Data: {x}"
@ -756,7 +756,7 @@ class TestServerResources:
mcp = FastMCP()
@mcp.resource("function://test", name="test_get_data")
def get_data() -> str:
def get_data() -> str: # pragma: no cover
"""get_data returns a string"""
return "Hello, world!"
@ -780,7 +780,7 @@ class TestServerResourceTemplates:
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://data")
def get_data_fn(param: str) -> str:
def get_data_fn(param: str) -> str: # pragma: no cover
return f"Data: {param}"
@pytest.mark.anyio
@ -791,7 +791,7 @@ class TestServerResourceTemplates:
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://{param}")
def get_data() -> str:
def get_data() -> str: # pragma: no cover
return "Data"
@pytest.mark.anyio
@ -800,7 +800,7 @@ class TestServerResourceTemplates:
mcp = FastMCP()
@mcp.resource("resource://{param}")
def get_data(param) -> str: # type: ignore
def get_data(param) -> str: # type: ignore # pragma: no cover
return "Data"
@pytest.mark.anyio
@ -825,7 +825,7 @@ class TestServerResourceTemplates:
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://{name}/data")
def get_data(user: str) -> str:
def get_data(user: str) -> str: # pragma: no cover
return f"Data for {user}"
@pytest.mark.anyio
@ -850,10 +850,10 @@ class TestServerResourceTemplates:
with pytest.raises(ValueError, match="Mismatch between URI parameters"):
@mcp.resource("resource://{org}/{repo}/data")
def get_data_mismatched(org: str, repo_2: str) -> str:
def get_data_mismatched(org: str, repo_2: str) -> str: # pragma: no cover
return f"Data for {org}"
"""Test that a resource with no parameters works as a regular resource"""
"""Test that a resource with no parameters works as a regular resource""" # pragma: no cover
mcp = FastMCP()
@mcp.resource("resource://static")
@ -914,7 +914,7 @@ class TestContextInjection:
"""Test that context parameters are properly detected."""
mcp = FastMCP()
def tool_with_context(x: int, ctx: Context[ServerSession, None]) -> str:
def tool_with_context(x: int, ctx: Context[ServerSession, None]) -> str: # pragma: no cover
return f"Request {ctx.request_id}: {x}"
tool = mcp._tool_manager.add_tool(tool_with_context)
@ -1223,7 +1223,7 @@ class TestServerPrompts:
with pytest.raises(TypeError, match="decorator was used incorrectly"):
@mcp.prompt # type: ignore
def fn() -> str:
def fn() -> str: # pragma: no cover
return "Hello, world!"
@pytest.mark.anyio
@ -1232,7 +1232,7 @@ class TestServerPrompts:
mcp = FastMCP()
@mcp.prompt()
def fn(name: str, optional: str = "default") -> str:
def fn(name: str, optional: str = "default") -> str: # pragma: no cover
return f"Hello, {name}!"
async with client_session(mcp._mcp_server) as client:
@ -1350,7 +1350,7 @@ class TestServerPrompts:
mcp = FastMCP()
@mcp.prompt()
def prompt_fn(name: str) -> str:
def prompt_fn(name: str) -> str: # pragma: no cover
return f"Hello, {name}!"
async with client_session(mcp._mcp_server) as client:

View file

@ -18,23 +18,23 @@ async def test_tool_title_precedence():
# Tool with only name
@mcp.tool(description="Basic tool")
def basic_tool(message: str) -> str:
def basic_tool(message: str) -> str: # pragma: no cover
return message
# Tool with title
@mcp.tool(description="Tool with title", title="User-Friendly Tool")
def tool_with_title(message: str) -> str:
def tool_with_title(message: str) -> str: # pragma: no cover
return message
# Tool with annotations.title (when title is not supported on decorator)
# We'll need to add this manually after registration
@mcp.tool(description="Tool with annotations")
def tool_with_annotations(message: str) -> str:
def tool_with_annotations(message: str) -> str: # pragma: no cover
return message
# Tool with both title and annotations.title
@mcp.tool(description="Tool with both", title="Primary Title")
def tool_with_both(message: str) -> str:
def tool_with_both(message: str) -> str: # pragma: no cover
return message
# Start server and connect client
@ -73,12 +73,12 @@ async def test_prompt_title():
# Prompt with only name
@mcp.prompt(description="Basic prompt")
def basic_prompt(topic: str) -> str:
def basic_prompt(topic: str) -> str: # pragma: no cover
return f"Tell me about {topic}"
# Prompt with title
@mcp.prompt(description="Titled prompt", title="Ask About Topic")
def titled_prompt(topic: str) -> str:
def titled_prompt(topic: str) -> str: # pragma: no cover
return f"Tell me about {topic}"
# Start server and connect client
@ -107,7 +107,7 @@ async def test_resource_title():
mcp = FastMCP(name="ResourceTitleServer")
# Static resource without title
def get_basic_data() -> str:
def get_basic_data() -> str: # pragma: no cover
return "Basic data"
basic_resource = FunctionResource(
@ -119,7 +119,7 @@ async def test_resource_title():
mcp.add_resource(basic_resource)
# Static resource with title
def get_titled_data() -> str:
def get_titled_data() -> str: # pragma: no cover
return "Titled data"
titled_resource = FunctionResource(
@ -133,12 +133,12 @@ async def test_resource_title():
# Dynamic resource without title
@mcp.resource("resource://dynamic/{id}")
def dynamic_resource(id: str) -> str:
def dynamic_resource(id: str) -> str: # pragma: no cover
return f"Data for {id}"
# Dynamic resource with title (when supported)
@mcp.resource("resource://titled-dynamic/{id}", title="Dynamic Data")
def titled_dynamic_resource(id: str) -> str:
def titled_dynamic_resource(id: str) -> str: # pragma: no cover
return f"Data for {id}"
# Start server and connect client
@ -171,7 +171,7 @@ async def test_resource_title():
assert dynamic.name == "dynamic_resource"
# Verify titled dynamic resource template (when supported)
if "resource://titled-dynamic/{id}" in templates:
if "resource://titled-dynamic/{id}" in templates: # pragma: no branch
titled_dynamic = templates["resource://titled-dynamic/{id}"]
assert titled_dynamic.title == "Dynamic Data"

View file

@ -19,7 +19,7 @@ class TestAddTools:
def test_basic_function(self):
"""Test registering and running a basic function."""
def sum(a: int, b: int) -> int:
def sum(a: int, b: int) -> int: # pragma: no cover
"""Add two numbers."""
return a + b
@ -35,7 +35,7 @@ class TestAddTools:
assert tool.parameters["properties"]["b"]["type"] == "integer"
def test_init_with_tools(self, caplog: pytest.LogCaptureFixture):
def sum(a: int, b: int) -> int:
def sum(a: int, b: int) -> int: # pragma: no cover
return a + b
class AddArguments(ArgModelBase):
@ -68,7 +68,7 @@ class TestAddTools:
async def test_async_function(self):
"""Test registering and running an async function."""
async def fetch_data(url: str) -> str:
async def fetch_data(url: str) -> str: # pragma: no cover
"""Fetch data from URL."""
return f"Data from {url}"
@ -89,7 +89,7 @@ class TestAddTools:
name: str
age: int
def create_user(user: UserInput, flag: bool) -> dict[str, Any]:
def create_user(user: UserInput, flag: bool) -> dict[str, Any]: # pragma: no cover
"""Create a new user."""
return {"id": 1, **user.model_dump()}
@ -112,7 +112,7 @@ class TestAddTools:
def __init__(self):
self.__name__ = "MyTool"
def __call__(self, x: int) -> int:
def __call__(self, x: int) -> int: # pragma: no cover
return x * 2
manager = ToolManager()
@ -129,7 +129,7 @@ class TestAddTools:
def __init__(self):
self.__name__ = "MyAsyncTool"
async def __call__(self, x: int) -> int:
async def __call__(self, x: int) -> int: # pragma: no cover
return x * 2
manager = ToolManager()
@ -156,7 +156,7 @@ class TestAddTools:
def test_warn_on_duplicate_tools(self, caplog: pytest.LogCaptureFixture):
"""Test warning on duplicate tools."""
def f(x: int) -> int:
def f(x: int) -> int: # pragma: no cover
return x
manager = ToolManager()
@ -168,7 +168,7 @@ class TestAddTools:
def test_disable_warn_on_duplicate_tools(self, caplog: pytest.LogCaptureFixture):
"""Test disabling warning on duplicate tools."""
def f(x: int) -> int:
def f(x: int) -> int: # pragma: no cover
return x
manager = ToolManager()
@ -182,7 +182,7 @@ class TestAddTools:
class TestCallTools:
@pytest.mark.anyio
async def test_call_tool(self):
def sum(a: int, b: int) -> int:
def sum(a: int, b: int) -> int: # pragma: no cover
"""Add two numbers."""
return a + b
@ -193,7 +193,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_async_tool(self):
async def double(n: int) -> int:
async def double(n: int) -> int: # pragma: no cover
"""Double a number."""
return n * 2
@ -243,7 +243,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_tool_with_missing_args(self):
def sum(a: int, b: int) -> int:
def sum(a: int, b: int) -> int: # pragma: no cover
"""Add two numbers."""
return a + b
@ -260,7 +260,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_tool_with_list_int_input(self):
def sum_vals(vals: list[int]) -> int:
def sum_vals(vals: list[int]) -> int: # pragma: no cover
return sum(vals)
manager = ToolManager()
@ -273,7 +273,7 @@ class TestCallTools:
@pytest.mark.anyio
async def test_call_tool_with_list_str_or_str_input(self):
def concat_strs(vals: list[str] | str) -> str:
def concat_strs(vals: list[str] | str) -> str: # pragma: no cover
return vals if isinstance(vals, str) else "".join(vals)
manager = ToolManager()
@ -297,7 +297,7 @@ class TestCallTools:
shrimp: list[Shrimp]
x: None
def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]:
def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]: # pragma: no cover
return [x.name for x in tank.shrimp]
manager = ToolManager()
@ -317,7 +317,7 @@ class TestCallTools:
class TestToolSchema:
@pytest.mark.anyio
async def test_context_arg_excluded_from_schema(self):
def something(a: int, ctx: Context[ServerSessionT, None]) -> int:
def something(a: int, ctx: Context[ServerSessionT, None]) -> int: # pragma: no cover
return a
manager = ToolManager()
@ -334,20 +334,22 @@ class TestContextHandling:
"""Test that context parameters are properly detected in
Tool.from_function()."""
def tool_with_context(x: int, ctx: Context[ServerSessionT, None]) -> str:
def tool_with_context(x: int, ctx: Context[ServerSessionT, None]) -> str: # pragma: no cover
return str(x)
manager = ToolManager()
tool = manager.add_tool(tool_with_context)
assert tool.context_kwarg == "ctx"
def tool_without_context(x: int) -> str:
def tool_without_context(x: int) -> str: # pragma: no cover
return str(x)
tool = manager.add_tool(tool_without_context)
assert tool.context_kwarg is None
def tool_with_parametrized_context(x: int, ctx: Context[ServerSessionT, LifespanContextT, RequestT]) -> str:
def tool_with_parametrized_context(
x: int, ctx: Context[ServerSessionT, LifespanContextT, RequestT]
) -> str: # pragma: no cover
return str(x)
tool = manager.add_tool(tool_with_parametrized_context)
@ -373,7 +375,7 @@ class TestContextHandling:
async def test_context_injection_async(self):
"""Test that context is properly injected in async tools."""
async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str:
async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str: # pragma: no cover
assert isinstance(ctx, Context)
return str(x)
@ -418,7 +420,7 @@ class TestToolAnnotations:
def test_tool_annotations(self):
"""Test that tool annotations are correctly added to tools."""
def read_data(path: str) -> str:
def read_data(path: str) -> str: # pragma: no cover
"""Read data from a file."""
return f"Data from {path}"
@ -443,7 +445,7 @@ class TestToolAnnotations:
app = FastMCP()
@app.tool(annotations=ToolAnnotations(title="Echo Tool", readOnlyHint=True))
def echo(message: str) -> str:
def echo(message: str) -> str: # pragma: no cover
"""Echo a message back."""
return message
@ -465,7 +467,7 @@ class TestStructuredOutput:
name: str
age: int
def get_user(user_id: int) -> UserOutput:
def get_user(user_id: int) -> UserOutput: # pragma: no cover
"""Get user by ID."""
return UserOutput(name="John", age=30)
@ -479,7 +481,7 @@ class TestStructuredOutput:
async def test_tool_with_primitive_output(self):
"""Test tool with primitive return type."""
def double_number(n: int) -> int:
def double_number(n: int) -> int: # pragma: no cover
"""Double a number."""
return 10
@ -500,7 +502,7 @@ class TestStructuredOutput:
expected_output = {"name": "Alice", "age": 25}
def get_user_dict(user_id: int) -> UserDict:
def get_user_dict(user_id: int) -> UserDict: # pragma: no cover
"""Get user as dict."""
return UserDict(name="Alice", age=25)
@ -520,7 +522,7 @@ class TestStructuredOutput:
expected_output = {"name": "Bob", "age": 40}
def get_person() -> Person:
def get_person() -> Person: # pragma: no cover
"""Get a person."""
return Person("Bob", 40)
@ -537,7 +539,7 @@ class TestStructuredOutput:
expected_list = [1, 2, 3, 4, 5]
expected_output = {"result": expected_list}
def get_numbers() -> list[int]:
def get_numbers() -> list[int]: # pragma: no cover
"""Get a list of numbers."""
return expected_list
@ -569,7 +571,7 @@ class TestStructuredOutput:
name: str
age: int
def get_user() -> UserOutput:
def get_user() -> UserOutput: # pragma: no cover
return UserOutput(name="Test", age=25)
manager = ToolManager()
@ -588,7 +590,7 @@ class TestStructuredOutput:
async def test_tool_with_dict_str_any_output(self):
"""Test tool with dict[str, Any] return type."""
def get_config() -> dict[str, Any]:
def get_config() -> dict[str, Any]: # pragma: no cover
"""Get configuration"""
return {"debug": True, "port": 8080, "features": ["auth", "logging"]}
@ -613,7 +615,7 @@ class TestStructuredOutput:
async def test_tool_with_dict_str_typed_output(self):
"""Test tool with dict[str, T] return type for specific T."""
def get_scores() -> dict[str, int]:
def get_scores() -> dict[str, int]: # pragma: no cover
"""Get player scores"""
return {"alice": 100, "bob": 85, "charlie": 92}
@ -641,7 +643,7 @@ class TestToolMetadata:
def test_add_tool_with_metadata(self):
"""Test adding a tool with metadata via ToolManager."""
def process_data(input_data: str) -> str:
def process_data(input_data: str) -> str: # pragma: no cover
"""Process some data."""
return f"Processed: {input_data}"
@ -658,7 +660,7 @@ class TestToolMetadata:
def test_add_tool_without_metadata(self):
"""Test that tools without metadata have None as meta value."""
def simple_tool(x: int) -> int:
def simple_tool(x: int) -> int: # pragma: no cover
"""Simple tool."""
return x * 2
@ -676,7 +678,7 @@ class TestToolMetadata:
metadata = {"client": {"ui_component": "file_picker"}, "priority": "high"}
@app.tool(meta=metadata)
def upload_file(filename: str) -> str:
def upload_file(filename: str) -> str: # pragma: no cover
"""Upload a file."""
return f"Uploaded: {filename}"
@ -700,7 +702,7 @@ class TestToolMetadata:
}
@app.tool(meta=metadata)
def analyze_text(text: str) -> dict[str, Any]:
def analyze_text(text: str) -> dict[str, Any]: # pragma: no cover
"""Analyze text content."""
return {"length": len(text), "words": len(text.split())}
@ -719,17 +721,17 @@ class TestToolMetadata:
metadata2 = {"ui": "picker", "experimental": True}
@app.tool(meta=metadata1)
def tool1(x: int) -> int:
def tool1(x: int) -> int: # pragma: no cover
"""First tool."""
return x
@app.tool(meta=metadata2)
def tool2(y: str) -> str:
def tool2(y: str) -> str: # pragma: no cover
"""Second tool."""
return y
@app.tool()
def tool3(z: bool) -> bool:
def tool3(z: bool) -> bool: # pragma: no cover
"""Third tool without metadata."""
return z
@ -746,7 +748,7 @@ class TestToolMetadata:
def test_metadata_with_complex_structure(self):
"""Test metadata with complex nested structures."""
def complex_tool(data: str) -> str:
def complex_tool(data: str) -> str: # pragma: no cover
"""Tool with complex metadata."""
return data
@ -775,7 +777,7 @@ class TestToolMetadata:
def test_metadata_empty_dict(self):
"""Test that empty dict metadata is preserved."""
def tool_with_empty_meta(x: int) -> int:
def tool_with_empty_meta(x: int) -> int: # pragma: no cover
"""Tool with empty metadata."""
return x
@ -795,7 +797,7 @@ class TestToolMetadata:
annotations = ToolAnnotations(title="Combined Tool", readOnlyHint=True)
@app.tool(meta=metadata, annotations=annotations)
def combined_tool(data: str) -> str:
def combined_tool(data: str) -> str: # pragma: no cover
"""Tool with both metadata and annotations."""
return data
@ -813,7 +815,7 @@ class TestRemoveTools:
def test_remove_existing_tool(self):
"""Test removing an existing tool."""
def add(a: int, b: int) -> int:
def add(a: int, b: int) -> int: # pragma: no cover
"""Add two numbers."""
return a + b
@ -841,15 +843,15 @@ class TestRemoveTools:
def test_remove_tool_from_multiple_tools(self):
"""Test removing one tool when multiple tools exist."""
def add(a: int, b: int) -> int:
def add(a: int, b: int) -> int: # pragma: no cover
"""Add two numbers."""
return a + b
def multiply(a: int, b: int) -> int:
def multiply(a: int, b: int) -> int: # pragma: no cover
"""Multiply two numbers."""
return a * b
def divide(a: int, b: int) -> float:
def divide(a: int, b: int) -> float: # pragma: no cover
"""Divide two numbers."""
return a / b
@ -877,7 +879,7 @@ class TestRemoveTools:
async def test_call_removed_tool_raises_error(self):
"""Test that calling a removed tool raises ToolError."""
def greet(name: str) -> str:
def greet(name: str) -> str: # pragma: no cover
"""Greet someone."""
return f"Hello, {name}!"
@ -898,7 +900,7 @@ class TestRemoveTools:
def test_remove_tool_case_sensitive(self):
"""Test that tool removal is case-sensitive."""
def test_func() -> str:
def test_func() -> str: # pragma: no cover
"""Test function."""
return "test"

View file

@ -121,7 +121,7 @@ async def test_untyped_request_param_is_deprecated() -> None:
"""Test: def foo(req) - should call without request."""
called = False
async def handler(req): # type: ignore[no-untyped-def] # pyright: ignore[reportMissingParameterType]
async def handler(req): # type: ignore[no-untyped-def] # pyright: ignore[reportMissingParameterType] # pragma: no cover
nonlocal called
called = True
return ["test"]
@ -139,7 +139,7 @@ async def test_untyped_request_param_is_deprecated() -> None:
async def test_any_typed_request_param_is_deprecated() -> None:
"""Test: def foo(req: Any) - should call without request."""
async def handler(req: Any) -> list[str]:
async def handler(req: Any) -> list[str]: # pragma: no cover
return ["test"]
wrapper = create_call_wrapper(handler, ListPromptsRequest)
@ -155,7 +155,7 @@ async def test_any_typed_request_param_is_deprecated() -> None:
async def test_generic_typed_request_param_is_deprecated() -> None:
"""Test: def foo(req: Generic[T]) - should call without request."""
async def handler(req: Generic[T]) -> list[str]: # pyright: ignore[reportGeneralTypeIssues]
async def handler(req: Generic[T]) -> list[str]: # pyright: ignore[reportGeneralTypeIssues] # pragma: no cover
return ["test"]
wrapper = create_call_wrapper(handler, ListPromptsRequest)
@ -171,7 +171,7 @@ async def test_generic_typed_request_param_is_deprecated() -> None:
async def test_wrong_typed_request_param_is_deprecated() -> None:
"""Test: def foo(req: str) - should call without request."""
async def handler(req: str) -> list[str]:
async def handler(req: str) -> list[str]: # pragma: no cover
return ["test"]
wrapper = create_call_wrapper(handler, ListPromptsRequest)
@ -188,7 +188,7 @@ async def test_required_param_before_typed_request_attempts_to_pass() -> None:
"""Test: def foo(thing: int, req: ListPromptsRequest) - attempts to pass request (will fail at runtime)."""
received_request = None
async def handler(thing: int, req: ListPromptsRequest) -> list[str]:
async def handler(thing: int, req: ListPromptsRequest) -> list[str]: # pragma: no cover
nonlocal received_request
received_request = req
return ["test"]
@ -280,7 +280,7 @@ async def test_different_request_types() -> None:
async def test_mixed_params_with_typed_request() -> None:
"""Test: def foo(a: str, req: ListPromptsRequest, b: int = 5) - attempts to pass request."""
async def handler(a: str, req: ListPromptsRequest, b: int = 5) -> list[str]:
async def handler(a: str, req: ListPromptsRequest, b: int = 5) -> list[str]: # pragma: no cover
return ["test"]
wrapper = create_call_wrapper(handler, ListPromptsRequest)

View file

@ -52,7 +52,7 @@ async def test_server_remains_functional_after_cancel():
ev_first_call.set()
await anyio.sleep(5) # First call is slow
return [types.TextContent(type="text", text=f"Call number: {call_count}")]
raise ValueError(f"Unknown tool: {name}")
raise ValueError(f"Unknown tool: {name}") # pragma: no cover
async with create_connected_server_and_client_session(server) as client:
# First request (will be cancelled)
@ -66,7 +66,7 @@ async def test_server_remains_functional_after_cancel():
),
CallToolResult,
)
pytest.fail("First request should have been cancelled")
pytest.fail("First request should have been cancelled") # pragma: no cover
except McpError:
pass # Expected

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