Get baseline 100% clean coverage (#1553)
This commit is contained in:
parent
da4fce2195
commit
89e9c43acf
31 changed files with 1032 additions and 1084 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue