style: Fix imports and line length formatting

This commit is contained in:
David Soria Parra
2024-12-19 22:33:40 +00:00
parent 7bbf71e29a
commit a79f51f55f
37 changed files with 242 additions and 135 deletions

View File

@@ -1,16 +1,18 @@
from pydantic import FileUrl
import pytest
from pydantic import FileUrl
from mcp.server.fastmcp.prompts.base import (
Prompt,
UserMessage,
TextContent,
AssistantMessage,
Message,
Prompt,
TextContent,
UserMessage,
)
from mcp.types import EmbeddedResource, TextResourceContents
class TestRenderPrompt:
@pytest.mark.anyio
async def test_basic_fn(self):
def fn() -> str:
return "Hello, world!"
@@ -20,6 +22,7 @@ class TestRenderPrompt:
UserMessage(content=TextContent(type="text", text="Hello, world!"))
]
@pytest.mark.anyio
async def test_async_fn(self):
async def fn() -> str:
return "Hello, world!"
@@ -29,6 +32,7 @@ class TestRenderPrompt:
UserMessage(content=TextContent(type="text", text="Hello, world!"))
]
@pytest.mark.anyio
async def test_fn_with_args(self):
async def fn(name: str, age: int = 30) -> str:
return f"Hello, {name}! You're {age} years old."
@@ -42,6 +46,7 @@ class TestRenderPrompt:
)
]
@pytest.mark.anyio
async def test_fn_with_invalid_kwargs(self):
async def fn(name: str, age: int = 30) -> str:
return f"Hello, {name}! You're {age} years old."
@@ -50,6 +55,7 @@ class TestRenderPrompt:
with pytest.raises(ValueError):
await prompt.render(arguments=dict(age=40))
@pytest.mark.anyio
async def test_fn_returns_message(self):
async def fn() -> UserMessage:
return UserMessage(content="Hello, world!")
@@ -59,6 +65,7 @@ class TestRenderPrompt:
UserMessage(content=TextContent(type="text", text="Hello, world!"))
]
@pytest.mark.anyio
async def test_fn_returns_assistant_message(self):
async def fn() -> AssistantMessage:
return AssistantMessage(
@@ -70,6 +77,7 @@ class TestRenderPrompt:
AssistantMessage(content=TextContent(type="text", text="Hello, world!"))
]
@pytest.mark.anyio
async def test_fn_returns_multiple_messages(self):
expected = [
UserMessage("Hello, world!"),
@@ -83,6 +91,7 @@ class TestRenderPrompt:
prompt = Prompt.from_function(fn)
assert await prompt.render() == expected
@pytest.mark.anyio
async def test_fn_returns_list_of_strings(self):
expected = [
"Hello, world!",
@@ -95,6 +104,7 @@ class TestRenderPrompt:
prompt = Prompt.from_function(fn)
assert await prompt.render() == [UserMessage(t) for t in expected]
@pytest.mark.anyio
async def test_fn_returns_resource_content(self):
"""Test returning a message with resource content."""
@@ -124,6 +134,7 @@ class TestRenderPrompt:
)
]
@pytest.mark.anyio
async def test_fn_returns_mixed_content(self):
"""Test returning messages with mixed content types."""
@@ -163,6 +174,7 @@ class TestRenderPrompt:
),
]
@pytest.mark.anyio
async def test_fn_returns_dict_with_resource(self):
"""Test returning a dict with resource content."""

View File

@@ -1,5 +1,6 @@
import pytest
from mcp.server.fastmcp.prompts.base import UserMessage, TextContent, Prompt
from mcp.server.fastmcp.prompts.base import Prompt, TextContent, UserMessage
from mcp.server.fastmcp.prompts.manager import PromptManager
@@ -60,6 +61,7 @@ class TestPromptManager:
assert len(prompts) == 2
assert prompts == [prompt1, prompt2]
@pytest.mark.anyio
async def test_render_prompt(self):
"""Test rendering a prompt."""
@@ -74,6 +76,7 @@ class TestPromptManager:
UserMessage(content=TextContent(type="text", text="Hello, world!"))
]
@pytest.mark.anyio
async def test_render_prompt_with_args(self):
"""Test rendering a prompt with arguments."""
@@ -88,12 +91,14 @@ class TestPromptManager:
UserMessage(content=TextContent(type="text", text="Hello, World!"))
]
@pytest.mark.anyio
async def test_render_unknown_prompt(self):
"""Test rendering a non-existent prompt."""
manager = PromptManager()
with pytest.raises(ValueError, match="Unknown prompt: unknown"):
await manager.render_prompt("unknown")
@pytest.mark.anyio
async def test_render_prompt_with_missing_args(self):
"""Test rendering a prompt with missing required arguments."""

View File

@@ -1,8 +1,8 @@
import os
import pytest
from pathlib import Path
from tempfile import NamedTemporaryFile
import pytest
from pydantic import FileUrl
from mcp.server.fastmcp.resources import FileResource
@@ -53,6 +53,7 @@ class TestFileResource:
assert isinstance(resource.path, Path)
assert resource.path.is_absolute()
@pytest.mark.anyio
async def test_read_text_file(self, temp_file: Path):
"""Test reading a text file."""
resource = FileResource(
@@ -64,6 +65,7 @@ class TestFileResource:
assert content == "test content"
assert resource.mime_type == "text/plain"
@pytest.mark.anyio
async def test_read_binary_file(self, temp_file: Path):
"""Test reading a file as binary."""
resource = FileResource(
@@ -85,6 +87,7 @@ class TestFileResource:
path=Path("test.txt"),
)
@pytest.mark.anyio
async def test_missing_file_error(self, temp_file: Path):
"""Test error when file doesn't exist."""
# Create path to non-existent file
@@ -100,6 +103,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):
"""Test reading a file without permissions."""
temp_file.chmod(0o000) # Remove all permissions

View File

@@ -1,5 +1,6 @@
from pydantic import BaseModel, AnyUrl
import pytest
from pydantic import AnyUrl, BaseModel
from mcp.server.fastmcp.resources import FunctionResource
@@ -24,6 +25,7 @@ class TestFunctionResource:
assert resource.mime_type == "text/plain" # default
assert resource.fn == my_func
@pytest.mark.anyio
async def test_read_text(self):
"""Test reading text from a FunctionResource."""
@@ -39,6 +41,7 @@ class TestFunctionResource:
assert content == "Hello, world!"
assert resource.mime_type == "text/plain"
@pytest.mark.anyio
async def test_read_binary(self):
"""Test reading binary data from a FunctionResource."""
@@ -53,6 +56,7 @@ class TestFunctionResource:
content = await resource.read()
assert content == b"Hello, world!"
@pytest.mark.anyio
async def test_json_conversion(self):
"""Test automatic JSON conversion of non-string results."""
@@ -68,6 +72,7 @@ class TestFunctionResource:
assert isinstance(content, str)
assert '"key": "value"' in content
@pytest.mark.anyio
async def test_error_handling(self):
"""Test error handling in FunctionResource."""
@@ -82,6 +87,7 @@ class TestFunctionResource:
with pytest.raises(ValueError, match="Error reading resource function://test"):
await resource.read()
@pytest.mark.anyio
async def test_basemodel_conversion(self):
"""Test handling of BaseModel types."""
@@ -96,6 +102,7 @@ class TestFunctionResource:
content = await resource.read()
assert content == '{"name": "test"}'
@pytest.mark.anyio
async def test_custom_type_conversion(self):
"""Test handling of custom types."""

View File

@@ -1,6 +1,7 @@
import pytest
from pathlib import Path
from tempfile import NamedTemporaryFile
import pytest
from pydantic import AnyUrl, FileUrl
from mcp.server.fastmcp.resources import (
@@ -80,6 +81,7 @@ class TestResourceManager:
manager.add_resource(resource)
assert "Resource already exists" not in caplog.text
@pytest.mark.anyio
async def test_get_resource(self, temp_file: Path):
"""Test getting a resource by URI."""
manager = ResourceManager()
@@ -92,6 +94,7 @@ class TestResourceManager:
retrieved = await manager.get_resource(resource.uri)
assert retrieved == resource
@pytest.mark.anyio
async def test_get_resource_from_template(self):
"""Test getting a resource through a template."""
manager = ResourceManager()
@@ -111,6 +114,7 @@ class TestResourceManager:
content = await resource.read()
assert content == "Hello, world!"
@pytest.mark.anyio
async def test_get_unknown_resource(self):
"""Test getting a non-existent resource."""
manager = ResourceManager()

View File

@@ -1,4 +1,5 @@
import json
import pytest
from pydantic import BaseModel
@@ -45,6 +46,7 @@ class TestResourceTemplate:
assert template.matches("test://foo") is None
assert template.matches("other://foo/123") is None
@pytest.mark.anyio
async def test_create_resource(self):
"""Test creating a resource from a template."""
@@ -68,6 +70,7 @@ class TestResourceTemplate:
data = json.loads(content)
assert data == {"key": "foo", "value": 123}
@pytest.mark.anyio
async def test_template_error(self):
"""Test error handling in template resource creation."""
@@ -83,6 +86,7 @@ class TestResourceTemplate:
with pytest.raises(ValueError, match="Error creating resource from template"):
await template.create_resource("fail://test", {"x": "test"})
@pytest.mark.anyio
async def test_async_text_resource(self):
"""Test creating a text resource from async function."""
@@ -104,6 +108,7 @@ class TestResourceTemplate:
content = await resource.read()
assert content == "Hello, world!"
@pytest.mark.anyio
async def test_async_binary_resource(self):
"""Test creating a binary resource from async function."""
@@ -125,6 +130,7 @@ class TestResourceTemplate:
content = await resource.read()
assert content == b"test"
@pytest.mark.anyio
async def test_basemodel_conversion(self):
"""Test handling of BaseModel types."""
@@ -152,6 +158,7 @@ class TestResourceTemplate:
data = json.loads(content)
assert data == {"key": "foo", "value": 123}
@pytest.mark.anyio
async def test_custom_type_conversion(self):
"""Test handling of custom types."""

View File

@@ -90,6 +90,7 @@ class TestResourceValidation:
)
assert resource.mime_type == "application/json"
@pytest.mark.anyio
async def test_resource_read_abstract(self):
"""Test that Resource.read() is abstract."""

View File

@@ -1,8 +1,10 @@
import json
from mcp.server.fastmcp import FastMCP
import pytest
from pathlib import Path
import pytest
from mcp.server.fastmcp import FastMCP
@pytest.fixture()
def test_dir(tmp_path_factory) -> Path:
@@ -71,6 +73,7 @@ def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
return mcp
@pytest.mark.anyio
async def test_list_resources(mcp: FastMCP):
resources = await mcp.list_resources()
assert len(resources) == 4
@@ -83,6 +86,7 @@ async def test_list_resources(mcp: FastMCP):
]
@pytest.mark.anyio
async def test_read_resource_dir(mcp: FastMCP):
files = await mcp.read_resource("dir://test_dir")
files = json.loads(files)
@@ -94,11 +98,13 @@ async def test_read_resource_dir(mcp: FastMCP):
]
@pytest.mark.anyio
async def test_read_resource_file(mcp: FastMCP):
result = await mcp.read_resource("file://test_dir/example.py")
assert result == "print('hello world')"
@pytest.mark.anyio
async def test_delete_file(mcp: FastMCP, test_dir: Path):
await mcp.call_tool(
"delete_file", arguments=dict(path=str(test_dir / "example.py"))
@@ -106,6 +112,7 @@ async def test_delete_file(mcp: FastMCP, test_dir: Path):
assert not (test_dir / "example.py").exists()
@pytest.mark.anyio
async def test_delete_file_and_check_resources(mcp: FastMCP, test_dir: Path):
await mcp.call_tool(
"delete_file", arguments=dict(path=str(test_dir / "example.py"))

View File

@@ -85,6 +85,7 @@ def complex_arguments_fn(
return "ok!"
@pytest.mark.anyio
async def test_complex_function_runtime_arg_validation_non_json():
"""Test that basic non-JSON arguments are validated correctly"""
meta = func_metadata(complex_arguments_fn)
@@ -121,6 +122,7 @@ async def test_complex_function_runtime_arg_validation_non_json():
)
@pytest.mark.anyio
async def test_complex_function_runtime_arg_validation_with_json():
"""Test that JSON string arguments are parsed and validated correctly"""
meta = func_metadata(complex_arguments_fn)
@@ -140,7 +142,7 @@ async def test_complex_function_runtime_arg_validation_with_json():
"unannotated": "test",
"my_model_a": "{}", # JSON string
"my_model_a_forward_ref": "{}", # JSON string
"my_model_b": '{"how_many_shrimp": 5, "ok": {"x": 1}, "y": null}', # JSON string
"my_model_b": '{"how_many_shrimp": 5, "ok": {"x": 1}, "y": null}',
},
arguments_to_pass_directly=None,
)
@@ -197,6 +199,7 @@ def test_skip_names():
assert model.also_keep == 2.5 # type: ignore
@pytest.mark.anyio
async def test_lambda_function():
"""Test lambda function schema and validation"""
fn = lambda x, y=5: x # noqa: E731
@@ -297,7 +300,7 @@ def test_complex_function_json_schema():
},
"field_with_default_via_field_annotation_before_nondefault_arg": {
"default": 1,
"title": "Field With Default Via Field Annotation Before Nondefault Arg",
"title": "Field With Default Via Field Annotation Before Arg",
"type": "integer",
},
"unannotated": {"title": "unannotated", "type": "string"},
@@ -316,11 +319,7 @@ def test_complex_function_json_schema():
"type": "string",
},
"my_model_a_with_default": {
"allOf": [
{
"$ref": "#/$defs/SomeInputModelA"
}
],
"allOf": [{"$ref": "#/$defs/SomeInputModelA"}],
"default": {},
},
"an_int_with_default": {

View File

@@ -3,32 +3,34 @@ from pathlib import Path
from typing import TYPE_CHECKING, Union
import pytest
from mcp.shared.exceptions import McpError
from mcp.shared.memory import (
create_connected_server_and_client_session as client_session,
)
from mcp.types import (
ImageContent,
TextContent,
TextResourceContents,
BlobResourceContents,
)
from pydantic import AnyUrl
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.fastmcp.prompts.base import EmbeddedResource, Message, UserMessage
from mcp.server.fastmcp.resources import FileResource, FunctionResource
from mcp.server.fastmcp.utilities.types import Image
from mcp.shared.exceptions import McpError
from mcp.shared.memory import (
create_connected_server_and_client_session as client_session,
)
from mcp.types import (
BlobResourceContents,
ImageContent,
TextContent,
TextResourceContents,
)
if TYPE_CHECKING:
from mcp.server.fastmcp import Context
class TestServer:
@pytest.mark.anyio
async def test_create_server(self):
mcp = FastMCP()
assert mcp.name == "FastMCP"
@pytest.mark.anyio
async def test_add_tool_decorator(self):
mcp = FastMCP()
@@ -38,6 +40,7 @@ class TestServer:
assert len(mcp._tool_manager.list_tools()) == 1
@pytest.mark.anyio
async def test_add_tool_decorator_incorrect_usage(self):
mcp = FastMCP()
@@ -47,6 +50,7 @@ class TestServer:
def add(x: int, y: int) -> int:
return x + y
@pytest.mark.anyio
async def test_add_resource_decorator(self):
mcp = FastMCP()
@@ -56,6 +60,7 @@ class TestServer:
assert len(mcp._resource_manager._templates) == 1
@pytest.mark.anyio
async def test_add_resource_decorator_incorrect_usage(self):
mcp = FastMCP()
@@ -88,12 +93,14 @@ def mixed_content_tool_fn() -> list[Union[TextContent, ImageContent]]:
class TestServerTools:
@pytest.mark.anyio
async def test_add_tool(self):
mcp = FastMCP()
mcp.add_tool(tool_fn)
mcp.add_tool(tool_fn)
assert len(mcp._tool_manager.list_tools()) == 1
@pytest.mark.anyio
async def test_list_tools(self):
mcp = FastMCP()
mcp.add_tool(tool_fn)
@@ -101,6 +108,7 @@ class TestServerTools:
tools = await client.list_tools()
assert len(tools.tools) == 1
@pytest.mark.anyio
async def test_call_tool(self):
mcp = FastMCP()
mcp.add_tool(tool_fn)
@@ -109,6 +117,7 @@ class TestServerTools:
assert not hasattr(result, "error")
assert len(result.content) > 0
@pytest.mark.anyio
async def test_tool_exception_handling(self):
mcp = FastMCP()
mcp.add_tool(error_tool_fn)
@@ -120,6 +129,7 @@ class TestServerTools:
assert "Test error" in content.text
assert result.isError is True
@pytest.mark.anyio
async def test_tool_error_handling(self):
mcp = FastMCP()
mcp.add_tool(error_tool_fn)
@@ -131,6 +141,7 @@ class TestServerTools:
assert "Test error" in content.text
assert result.isError is True
@pytest.mark.anyio
async def test_tool_error_details(self):
"""Test that exception details are properly formatted in the response"""
mcp = FastMCP()
@@ -143,6 +154,7 @@ class TestServerTools:
assert "Test error" in content.text
assert result.isError is True
@pytest.mark.anyio
async def test_tool_return_value_conversion(self):
mcp = FastMCP()
mcp.add_tool(tool_fn)
@@ -153,6 +165,7 @@ class TestServerTools:
assert isinstance(content, TextContent)
assert content.text == "3"
@pytest.mark.anyio
async def test_tool_image_helper(self, tmp_path: Path):
# Create a test image
image_path = tmp_path / "test.png"
@@ -171,6 +184,7 @@ class TestServerTools:
decoded = base64.b64decode(content.data)
assert decoded == b"fake png data"
@pytest.mark.anyio
async def test_tool_mixed_content(self):
mcp = FastMCP()
mcp.add_tool(mixed_content_tool_fn)
@@ -185,8 +199,10 @@ class TestServerTools:
assert content2.mimeType == "image/png"
assert content2.data == "abc"
@pytest.mark.anyio
async def test_tool_mixed_list_with_image(self, tmp_path: Path):
"""Test that lists containing Image objects and other types are handled correctly"""
"""Test that lists containing Image objects and other types are handled
correctly"""
# Create a test image
image_path = tmp_path / "test.png"
image_path.write_bytes(b"test image data")
@@ -224,6 +240,7 @@ class TestServerTools:
class TestServerResources:
@pytest.mark.anyio
async def test_text_resource(self):
mcp = FastMCP()
@@ -240,6 +257,7 @@ class TestServerResources:
assert isinstance(result.contents[0], TextResourceContents)
assert result.contents[0].text == "Hello, world!"
@pytest.mark.anyio
async def test_binary_resource(self):
mcp = FastMCP()
@@ -259,6 +277,7 @@ class TestServerResources:
assert isinstance(result.contents[0], BlobResourceContents)
assert result.contents[0].blob == base64.b64encode(b"Binary data").decode()
@pytest.mark.anyio
async def test_file_resource_text(self, tmp_path: Path):
mcp = FastMCP()
@@ -276,6 +295,7 @@ class TestServerResources:
assert isinstance(result.contents[0], TextResourceContents)
assert result.contents[0].text == "Hello from file!"
@pytest.mark.anyio
async def test_file_resource_binary(self, tmp_path: Path):
mcp = FastMCP()
@@ -301,6 +321,7 @@ class TestServerResources:
class TestServerResourceTemplates:
@pytest.mark.anyio
async def test_resource_with_params(self):
"""Test that a resource with function parameters raises an error if the URI
parameters don't match"""
@@ -312,6 +333,7 @@ class TestServerResourceTemplates:
def get_data_fn(param: str) -> str:
return f"Data: {param}"
@pytest.mark.anyio
async def test_resource_with_uri_params(self):
"""Test that a resource with URI parameters is automatically a template"""
mcp = FastMCP()
@@ -322,6 +344,7 @@ class TestServerResourceTemplates:
def get_data() -> str:
return "Data"
@pytest.mark.anyio
async def test_resource_with_untyped_params(self):
"""Test that a resource with untyped parameters raises an error"""
mcp = FastMCP()
@@ -330,6 +353,7 @@ class TestServerResourceTemplates:
def get_data(param) -> str:
return "Data"
@pytest.mark.anyio
async def test_resource_matching_params(self):
"""Test that a resource with matching URI and function parameters works"""
mcp = FastMCP()
@@ -343,6 +367,7 @@ class TestServerResourceTemplates:
assert isinstance(result.contents[0], TextResourceContents)
assert result.contents[0].text == "Data for test"
@pytest.mark.anyio
async def test_resource_mismatched_params(self):
"""Test that mismatched parameters raise an error"""
mcp = FastMCP()
@@ -353,6 +378,7 @@ class TestServerResourceTemplates:
def get_data(user: str) -> str:
return f"Data for {user}"
@pytest.mark.anyio
async def test_resource_multiple_params(self):
"""Test that multiple parameters work correctly"""
mcp = FastMCP()
@@ -368,6 +394,7 @@ class TestServerResourceTemplates:
assert isinstance(result.contents[0], TextResourceContents)
assert result.contents[0].text == "Data for cursor/fastmcp"
@pytest.mark.anyio
async def test_resource_multiple_mismatched_params(self):
"""Test that mismatched parameters raise an error"""
mcp = FastMCP()
@@ -390,6 +417,7 @@ class TestServerResourceTemplates:
assert isinstance(result.contents[0], TextResourceContents)
assert result.contents[0].text == "Static data"
@pytest.mark.anyio
async def test_template_to_resource_conversion(self):
"""Test that templates are properly converted to resources when accessed"""
mcp = FastMCP()
@@ -412,6 +440,7 @@ class TestServerResourceTemplates:
class TestContextInjection:
"""Test context injection in tools."""
@pytest.mark.anyio
async def test_context_detection(self):
"""Test that context parameters are properly detected."""
mcp = FastMCP()
@@ -422,6 +451,7 @@ class TestContextInjection:
tool = mcp._tool_manager.add_tool(tool_with_context)
assert tool.context_kwarg == "ctx"
@pytest.mark.anyio
async def test_context_injection(self):
"""Test that context is properly injected into tool calls."""
mcp = FastMCP()
@@ -439,6 +469,7 @@ class TestContextInjection:
assert "Request" in content.text
assert "42" in content.text
@pytest.mark.anyio
async def test_async_context(self):
"""Test that context works in async functions."""
mcp = FastMCP()
@@ -456,6 +487,7 @@ class TestContextInjection:
assert "Async request" in content.text
assert "42" in content.text
@pytest.mark.anyio
async def test_context_logging(self):
"""Test that context logging methods work."""
mcp = FastMCP()
@@ -475,6 +507,7 @@ class TestContextInjection:
assert isinstance(content, TextContent)
assert "Logged messages for test" in content.text
@pytest.mark.anyio
async def test_optional_context(self):
"""Test that context is optional."""
mcp = FastMCP()
@@ -490,6 +523,7 @@ class TestContextInjection:
assert isinstance(content, TextContent)
assert content.text == "42"
@pytest.mark.anyio
async def test_context_resource_access(self):
"""Test that context can access resources."""
mcp = FastMCP()
@@ -514,6 +548,7 @@ class TestContextInjection:
class TestServerPrompts:
"""Test prompt functionality in FastMCP server."""
@pytest.mark.anyio
async def test_prompt_decorator(self):
"""Test that the prompt decorator registers prompts correctly."""
mcp = FastMCP()
@@ -530,6 +565,7 @@ class TestServerPrompts:
assert isinstance(content[0].content, TextContent)
assert content[0].content.text == "Hello, world!"
@pytest.mark.anyio
async def test_prompt_decorator_with_name(self):
"""Test prompt decorator with custom name."""
mcp = FastMCP()
@@ -545,6 +581,7 @@ class TestServerPrompts:
assert isinstance(content[0].content, TextContent)
assert content[0].content.text == "Hello, world!"
@pytest.mark.anyio
async def test_prompt_decorator_with_description(self):
"""Test prompt decorator with custom description."""
mcp = FastMCP()
@@ -569,6 +606,7 @@ class TestServerPrompts:
def fn() -> str:
return "Hello, world!"
@pytest.mark.anyio
async def test_list_prompts(self):
"""Test listing prompts through MCP protocol."""
mcp = FastMCP()
@@ -590,6 +628,7 @@ class TestServerPrompts:
assert prompt.arguments[1].name == "optional"
assert prompt.arguments[1].required is False
@pytest.mark.anyio
async def test_get_prompt(self):
"""Test getting a prompt through MCP protocol."""
mcp = FastMCP()
@@ -607,6 +646,7 @@ class TestServerPrompts:
assert isinstance(content, TextContent)
assert content.text == "Hello, World!"
@pytest.mark.anyio
async def test_get_prompt_with_resource(self):
"""Test getting a prompt that returns resource content."""
mcp = FastMCP()
@@ -636,6 +676,7 @@ class TestServerPrompts:
assert resource.text == "File contents"
assert resource.mimeType == "text/plain"
@pytest.mark.anyio
async def test_get_unknown_prompt(self):
"""Test error when getting unknown prompt."""
mcp = FastMCP()
@@ -643,6 +684,7 @@ class TestServerPrompts:
with pytest.raises(McpError, match="Unknown prompt"):
await client.get_prompt("unknown")
@pytest.mark.anyio
async def test_get_prompt_missing_args(self):
"""Test error when required arguments are missing."""
mcp = FastMCP()

View File

@@ -1,9 +1,10 @@
import json
import logging
from typing import Optional
import pytest
from pydantic import BaseModel
import json
from mcp.server.fastmcp.exceptions import ToolError
from mcp.server.fastmcp.tools import ToolManager
@@ -27,6 +28,7 @@ class TestAddTools:
assert tool.parameters["properties"]["a"]["type"] == "integer"
assert tool.parameters["properties"]["b"]["type"] == "integer"
@pytest.mark.anyio
async def test_async_function(self):
"""Test registering and running an async function."""
@@ -111,6 +113,7 @@ class TestAddTools:
class TestCallTools:
@pytest.mark.anyio
async def test_call_tool(self):
def add(a: int, b: int) -> int:
"""Add two numbers."""
@@ -121,6 +124,7 @@ class TestCallTools:
result = await manager.call_tool("add", {"a": 1, "b": 2})
assert result == 3
@pytest.mark.anyio
async def test_call_async_tool(self):
async def double(n: int) -> int:
"""Double a number."""
@@ -131,6 +135,7 @@ class TestCallTools:
result = await manager.call_tool("double", {"n": 5})
assert result == 10
@pytest.mark.anyio
async def test_call_tool_with_default_args(self):
def add(a: int, b: int = 1) -> int:
"""Add two numbers."""
@@ -141,6 +146,7 @@ class TestCallTools:
result = await manager.call_tool("add", {"a": 1})
assert result == 2
@pytest.mark.anyio
async def test_call_tool_with_missing_args(self):
def add(a: int, b: int) -> int:
"""Add two numbers."""
@@ -151,11 +157,13 @@ class TestCallTools:
with pytest.raises(ToolError):
await manager.call_tool("add", {"a": 1})
@pytest.mark.anyio
async def test_call_unknown_tool(self):
manager = ToolManager()
with pytest.raises(ToolError):
await manager.call_tool("unknown", {"a": 1})
@pytest.mark.anyio
async def test_call_tool_with_list_int_input(self):
def sum_vals(vals: list[int]) -> int:
return sum(vals)
@@ -168,6 +176,7 @@ class TestCallTools:
result = await manager.call_tool("sum_vals", {"vals": [1, 2, 3]})
assert result == 6
@pytest.mark.anyio
async def test_call_tool_with_list_str_or_str_input(self):
def concat_strs(vals: list[str] | str) -> str:
return vals if isinstance(vals, str) else "".join(vals)
@@ -184,6 +193,7 @@ class TestCallTools:
result = await manager.call_tool("concat_strs", {"vals": '"a"'})
assert result == '"a"'
@pytest.mark.anyio
async def test_call_tool_with_complex_model(self):
from mcp.server.fastmcp import Context
@@ -212,6 +222,7 @@ class TestCallTools:
class TestToolSchema:
@pytest.mark.anyio
async def test_context_arg_excluded_from_schema(self):
from mcp.server.fastmcp import Context
@@ -229,7 +240,8 @@ class TestContextHandling:
"""Test context handling in the tool manager."""
def test_context_parameter_detection(self):
"""Test that context parameters are properly detected in Tool.from_function()."""
"""Test that context parameters are properly detected in
Tool.from_function()."""
from mcp.server.fastmcp import Context
def tool_with_context(x: int, ctx: Context) -> str:
@@ -245,6 +257,7 @@ class TestContextHandling:
tool = manager.add_tool(tool_without_context)
assert tool.context_kwarg is None
@pytest.mark.anyio
async def test_context_injection(self):
"""Test that context is properly injected during tool execution."""
from mcp.server.fastmcp import Context, FastMCP
@@ -261,6 +274,7 @@ class TestContextHandling:
result = await manager.call_tool("tool_with_context", {"x": 42}, context=ctx)
assert result == "42"
@pytest.mark.anyio
async def test_context_injection_async(self):
"""Test that context is properly injected in async tools."""
from mcp.server.fastmcp import Context, FastMCP
@@ -277,6 +291,7 @@ class TestContextHandling:
result = await manager.call_tool("async_tool", {"x": 42}, context=ctx)
assert result == "42"
@pytest.mark.anyio
async def test_context_optional(self):
"""Test that context is optional when calling tools."""
from mcp.server.fastmcp import Context
@@ -290,6 +305,7 @@ class TestContextHandling:
result = await manager.call_tool("tool_with_context", {"x": 42})
assert result == "42"
@pytest.mark.anyio
async def test_context_error_handling(self):
"""Test error handling when context injection fails."""
from mcp.server.fastmcp import Context, FastMCP