refactor: use snake case instead of camel case in types (#1894)
This commit is contained in:
parent
7080fcf1e6
commit
e94b386a13
10 changed files with 1063 additions and 1057 deletions
|
|
@ -96,7 +96,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -109,7 +109,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -128,7 +128,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
),
|
||||
|
|
@ -144,7 +144,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
),
|
||||
|
|
@ -176,7 +176,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ async def test_elicitation_with_default_values():
|
|||
async def callback_schema_verify(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
# Verify the schema includes defaults
|
||||
assert isinstance(params, types.ElicitRequestFormParams), "Expected form mode elicitation"
|
||||
schema = params.requestedSchema
|
||||
schema = params.requested_schema
|
||||
props = schema["properties"]
|
||||
|
||||
assert props["name"]["default"] == "Guest"
|
||||
|
|
|
|||
|
|
@ -850,7 +850,7 @@ def test_tool_call_result_annotated_is_structured_and_converted():
|
|||
name: str
|
||||
|
||||
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
|
||||
return CallToolResult(content=[], structuredContent={"name": "Brandon"})
|
||||
return CallToolResult(content=[], structured_content={"name": "Brandon"})
|
||||
|
||||
meta = func_metadata(func_returning_annotated_tool_call_result)
|
||||
|
||||
|
|
@ -870,7 +870,7 @@ def test_tool_call_result_annotated_is_structured_and_invalid():
|
|||
name: str
|
||||
|
||||
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
|
||||
return CallToolResult(content=[], structuredContent={"person": "Brandon"})
|
||||
return CallToolResult(content=[], structured_content={"person": "Brandon"})
|
||||
|
||||
meta = func_metadata(func_returning_annotated_tool_call_result)
|
||||
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ async def test_basic_tools(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Tool Example"
|
||||
assert result.server_info.name == "Tool Example"
|
||||
assert result.capabilities.tools is not None
|
||||
|
||||
# Test sum tool
|
||||
|
|
@ -295,7 +295,7 @@ async def test_basic_resources(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Resource Example"
|
||||
assert result.server_info.name == "Resource Example"
|
||||
assert result.capabilities.resources is not None
|
||||
|
||||
# Test document resource
|
||||
|
|
@ -336,7 +336,7 @@ async def test_basic_prompts(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Prompt Example"
|
||||
assert result.server_info.name == "Prompt Example"
|
||||
assert result.capabilities.prompts is not None
|
||||
|
||||
# Test review_code prompt
|
||||
|
|
@ -396,7 +396,7 @@ async def test_tool_progress(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Progress Example"
|
||||
assert result.server_info.name == "Progress Example"
|
||||
|
||||
# Test progress callback
|
||||
progress_updates = []
|
||||
|
|
@ -449,7 +449,7 @@ async def test_sampling(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Sampling Example"
|
||||
assert result.server_info.name == "Sampling Example"
|
||||
assert result.capabilities.tools is not None
|
||||
|
||||
# Test sampling tool
|
||||
|
|
@ -480,7 +480,7 @@ async def test_elicitation(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Elicitation Example"
|
||||
assert result.server_info.name == "Elicitation Example"
|
||||
|
||||
# Test booking with unavailable date (triggers elicitation)
|
||||
booking_result = await session.call_tool(
|
||||
|
|
@ -537,7 +537,7 @@ async def test_notifications(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Notifications Example"
|
||||
assert result.server_info.name == "Notifications Example"
|
||||
|
||||
# Call tool that generates notifications
|
||||
tool_result = await session.call_tool("process_data", {"data": "test_data"})
|
||||
|
|
@ -578,7 +578,7 @@ async def test_completion(server_transport: str, server_url: str) -> None:
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Example"
|
||||
assert result.server_info.name == "Example"
|
||||
assert result.capabilities.resources is not None
|
||||
assert result.capabilities.prompts is not None
|
||||
|
||||
|
|
@ -635,7 +635,7 @@ async def test_fastmcp_quickstart(server_transport: str, server_url: str) -> Non
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Demo"
|
||||
assert result.server_info.name == "Demo"
|
||||
|
||||
# Test add tool
|
||||
tool_result = await session.call_tool("add", {"a": 10, "b": 20})
|
||||
|
|
@ -673,7 +673,7 @@ async def test_structured_output(server_transport: str, server_url: str) -> None
|
|||
# Test initialization
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
assert result.serverInfo.name == "Structured Output Example"
|
||||
assert result.server_info.name == "Structured Output Example"
|
||||
|
||||
# Test get_weather tool
|
||||
weather_result = await session.call_tool("get_weather", {"city": "New York"})
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ async def test_parameter_descriptions():
|
|||
tool = tools[0]
|
||||
|
||||
# Check that parameter descriptions are present in the schema
|
||||
properties = tool.inputSchema["properties"]
|
||||
properties = tool.input_schema["properties"]
|
||||
assert "name" in properties
|
||||
assert properties["name"]["description"] == "The name to greet"
|
||||
assert "title" in properties
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestServer:
|
|||
instructions="Server instructions",
|
||||
website_url="https://example.com/mcp_server",
|
||||
version="1.0",
|
||||
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png", sizes=["48x48", "96x96"])],
|
||||
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png", sizes=["48x48", "96x96"])],
|
||||
)
|
||||
assert mcp.name == "FastMCP"
|
||||
assert mcp.title == "FastMCP Server"
|
||||
|
|
@ -197,8 +197,8 @@ def audio_tool_fn(path: str) -> Audio:
|
|||
def mixed_content_tool_fn() -> list[ContentBlock]:
|
||||
return [
|
||||
TextContent(type="text", text="Hello"),
|
||||
ImageContent(type="image", data="abc", mimeType="image/png"),
|
||||
AudioContent(type="audio", data="def", mimeType="audio/wav"),
|
||||
ImageContent(type="image", data="abc", mime_type="image/png"),
|
||||
AudioContent(type="audio", data="def", mime_type="audio/wav"),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ class TestServerTools:
|
|||
content = result.content[0]
|
||||
assert isinstance(content, TextContent)
|
||||
assert "Test error" in content.text
|
||||
assert result.isError is True
|
||||
assert result.is_error is True
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_error_handling(self):
|
||||
|
|
@ -249,7 +249,7 @@ class TestServerTools:
|
|||
content = result.content[0]
|
||||
assert isinstance(content, TextContent)
|
||||
assert "Test error" in content.text
|
||||
assert result.isError is True
|
||||
assert result.is_error is True
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_error_details(self):
|
||||
|
|
@ -262,7 +262,7 @@ class TestServerTools:
|
|||
assert isinstance(content, TextContent)
|
||||
assert isinstance(content.text, str)
|
||||
assert "Test error" in content.text
|
||||
assert result.isError is True
|
||||
assert result.is_error is True
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_return_value_conversion(self):
|
||||
|
|
@ -275,8 +275,8 @@ class TestServerTools:
|
|||
assert isinstance(content, TextContent)
|
||||
assert content.text == "3"
|
||||
# Check structured content - int return type should have structured output
|
||||
assert result.structuredContent is not None
|
||||
assert result.structuredContent == {"result": 3}
|
||||
assert result.structured_content is not None
|
||||
assert result.structured_content == {"result": 3}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_image_helper(self, tmp_path: Path):
|
||||
|
|
@ -292,12 +292,12 @@ class TestServerTools:
|
|||
content = result.content[0]
|
||||
assert isinstance(content, ImageContent)
|
||||
assert content.type == "image"
|
||||
assert content.mimeType == "image/png"
|
||||
assert content.mime_type == "image/png"
|
||||
# Verify base64 encoding
|
||||
decoded = base64.b64decode(content.data)
|
||||
assert decoded == b"fake png data"
|
||||
# Check structured content - Image return type should NOT have structured output
|
||||
assert result.structuredContent is None
|
||||
assert result.structured_content is None
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_audio_helper(self, tmp_path: Path):
|
||||
|
|
@ -313,12 +313,12 @@ class TestServerTools:
|
|||
content = result.content[0]
|
||||
assert isinstance(content, AudioContent)
|
||||
assert content.type == "audio"
|
||||
assert content.mimeType == "audio/wav"
|
||||
assert content.mime_type == "audio/wav"
|
||||
# Verify base64 encoding
|
||||
decoded = base64.b64decode(content.data)
|
||||
assert decoded == b"fake wav data"
|
||||
# Check structured content - Image return type should NOT have structured output
|
||||
assert result.structuredContent is None
|
||||
assert result.structured_content is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename,expected_mime_type",
|
||||
|
|
@ -348,7 +348,7 @@ class TestServerTools:
|
|||
content = result.content[0]
|
||||
assert isinstance(content, AudioContent)
|
||||
assert content.type == "audio"
|
||||
assert content.mimeType == expected_mime_type
|
||||
assert content.mime_type == expected_mime_type
|
||||
# Verify base64 encoding
|
||||
decoded = base64.b64decode(content.data)
|
||||
assert decoded == b"fake audio data"
|
||||
|
|
@ -364,14 +364,14 @@ class TestServerTools:
|
|||
assert isinstance(content1, TextContent)
|
||||
assert content1.text == "Hello"
|
||||
assert isinstance(content2, ImageContent)
|
||||
assert content2.mimeType == "image/png"
|
||||
assert content2.mime_type == "image/png"
|
||||
assert content2.data == "abc"
|
||||
assert isinstance(content3, AudioContent)
|
||||
assert content3.mimeType == "audio/wav"
|
||||
assert content3.mime_type == "audio/wav"
|
||||
assert content3.data == "def"
|
||||
assert result.structuredContent is not None
|
||||
assert "result" in result.structuredContent
|
||||
structured_result = result.structuredContent["result"]
|
||||
assert result.structured_content is not None
|
||||
assert "result" in result.structured_content
|
||||
structured_result = result.structured_content["result"]
|
||||
assert len(structured_result) == 3
|
||||
|
||||
expected_content = [
|
||||
|
|
@ -419,12 +419,12 @@ class TestServerTools:
|
|||
# Check image conversion
|
||||
content2 = result.content[1]
|
||||
assert isinstance(content2, ImageContent)
|
||||
assert content2.mimeType == "image/png"
|
||||
assert content2.mime_type == "image/png"
|
||||
assert base64.b64decode(content2.data) == b"test image data"
|
||||
# Check audio conversion
|
||||
content3 = result.content[2]
|
||||
assert isinstance(content3, AudioContent)
|
||||
assert content3.mimeType == "audio/wav"
|
||||
assert content3.mime_type == "audio/wav"
|
||||
assert base64.b64decode(content3.data) == b"test audio data"
|
||||
# Check dict conversion
|
||||
content4 = result.content[3]
|
||||
|
|
@ -435,7 +435,7 @@ class TestServerTools:
|
|||
assert isinstance(content5, TextContent)
|
||||
assert content5.text == "direct content"
|
||||
# Check structured content - untyped list with Image objects should NOT have structured output
|
||||
assert result.structuredContent is None
|
||||
assert result.structured_content is None
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_structured_output_basemodel(self):
|
||||
|
|
@ -457,16 +457,16 @@ class TestServerTools:
|
|||
# Check that the tool has outputSchema
|
||||
tools = await client.list_tools()
|
||||
tool = next(t for t in tools.tools if t.name == "get_user")
|
||||
assert tool.outputSchema is not None
|
||||
assert tool.outputSchema["type"] == "object"
|
||||
assert "name" in tool.outputSchema["properties"]
|
||||
assert "age" in tool.outputSchema["properties"]
|
||||
assert tool.output_schema is not None
|
||||
assert tool.output_schema["type"] == "object"
|
||||
assert "name" in tool.output_schema["properties"]
|
||||
assert "age" in tool.output_schema["properties"]
|
||||
|
||||
# Call the tool and check structured output
|
||||
result = await client.call_tool("get_user", {"user_id": 123})
|
||||
assert result.isError is False
|
||||
assert result.structuredContent is not None
|
||||
assert result.structuredContent == {"name": "John Doe", "age": 30, "active": True}
|
||||
assert result.is_error is False
|
||||
assert result.structured_content is not None
|
||||
assert result.structured_content == {"name": "John Doe", "age": 30, "active": True}
|
||||
# Content should be JSON serialized version
|
||||
assert len(result.content) == 1
|
||||
assert isinstance(result.content[0], TextContent)
|
||||
|
|
@ -487,17 +487,17 @@ class TestServerTools:
|
|||
# Check that the tool has outputSchema
|
||||
tools = await client.list_tools()
|
||||
tool = next(t for t in tools.tools if t.name == "calculate_sum")
|
||||
assert tool.outputSchema is not None
|
||||
assert tool.output_schema is not None
|
||||
# Primitive types are wrapped
|
||||
assert tool.outputSchema["type"] == "object"
|
||||
assert "result" in tool.outputSchema["properties"]
|
||||
assert tool.outputSchema["properties"]["result"]["type"] == "integer"
|
||||
assert tool.output_schema["type"] == "object"
|
||||
assert "result" in tool.output_schema["properties"]
|
||||
assert tool.output_schema["properties"]["result"]["type"] == "integer"
|
||||
|
||||
# Call the tool
|
||||
result = await client.call_tool("calculate_sum", {"a": 5, "b": 7})
|
||||
assert result.isError is False
|
||||
assert result.structuredContent is not None
|
||||
assert result.structuredContent == {"result": 12}
|
||||
assert result.is_error is False
|
||||
assert result.structured_content is not None
|
||||
assert result.structured_content == {"result": 12}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_structured_output_list(self):
|
||||
|
|
@ -512,9 +512,9 @@ class TestServerTools:
|
|||
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
result = await client.call_tool("get_numbers", {})
|
||||
assert result.isError is False
|
||||
assert result.structuredContent is not None
|
||||
assert result.structuredContent == {"result": [1, 2, 3, 4, 5]}
|
||||
assert result.is_error is False
|
||||
assert result.structured_content is not None
|
||||
assert result.structured_content == {"result": [1, 2, 3, 4, 5]}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_structured_output_server_side_validation_error(self):
|
||||
|
|
@ -528,8 +528,8 @@ class TestServerTools:
|
|||
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
result = await client.call_tool("get_numbers", {})
|
||||
assert result.isError is True
|
||||
assert result.structuredContent is None
|
||||
assert result.is_error is True
|
||||
assert result.structured_content is None
|
||||
assert len(result.content) == 1
|
||||
assert isinstance(result.content[0], TextContent)
|
||||
|
||||
|
|
@ -554,17 +554,18 @@ class TestServerTools:
|
|||
# Check schema
|
||||
tools = await client.list_tools()
|
||||
tool = next(t for t in tools.tools if t.name == "get_metadata")
|
||||
assert tool.outputSchema is not None
|
||||
assert tool.outputSchema["type"] == "object"
|
||||
assert tool.output_schema is not None
|
||||
assert tool.output_schema["type"] == "object"
|
||||
# dict[str, Any] should have minimal schema
|
||||
assert (
|
||||
"additionalProperties" not in tool.outputSchema or tool.outputSchema.get("additionalProperties") is True
|
||||
"additionalProperties" not in tool.output_schema
|
||||
or tool.output_schema.get("additionalProperties") is True
|
||||
)
|
||||
|
||||
# Call tool
|
||||
result = await client.call_tool("get_metadata", {})
|
||||
assert result.isError is False
|
||||
assert result.structuredContent is not None
|
||||
assert result.is_error is False
|
||||
assert result.structured_content is not None
|
||||
expected = {
|
||||
"version": "1.0.0",
|
||||
"enabled": True,
|
||||
|
|
@ -572,7 +573,7 @@ class TestServerTools:
|
|||
"tags": ["production", "stable"],
|
||||
"config": {"nested": {"value": 123}},
|
||||
}
|
||||
assert result.structuredContent == expected
|
||||
assert result.structured_content == expected
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_structured_output_dict_str_typed(self):
|
||||
|
|
@ -589,14 +590,14 @@ class TestServerTools:
|
|||
# Check schema
|
||||
tools = await client.list_tools()
|
||||
tool = next(t for t in tools.tools if t.name == "get_settings")
|
||||
assert tool.outputSchema is not None
|
||||
assert tool.outputSchema["type"] == "object"
|
||||
assert tool.outputSchema["additionalProperties"]["type"] == "string"
|
||||
assert tool.output_schema is not None
|
||||
assert tool.output_schema["type"] == "object"
|
||||
assert tool.output_schema["additionalProperties"]["type"] == "string"
|
||||
|
||||
# Call tool
|
||||
result = await client.call_tool("get_settings", {})
|
||||
assert result.isError is False
|
||||
assert result.structuredContent == {"theme": "dark", "language": "en", "timezone": "UTC"}
|
||||
assert result.is_error is False
|
||||
assert result.structured_content == {"theme": "dark", "language": "en", "timezone": "UTC"}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_remove_tool(self):
|
||||
|
|
@ -656,7 +657,7 @@ class TestServerTools:
|
|||
# Verify tool works before removal
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
|
||||
assert not result.isError
|
||||
assert not result.is_error
|
||||
content = result.content[0]
|
||||
assert isinstance(content, TextContent)
|
||||
assert content.text == "3"
|
||||
|
|
@ -667,7 +668,7 @@ class TestServerTools:
|
|||
# Verify calling removed tool returns an error
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
|
||||
assert result.isError
|
||||
assert result.is_error
|
||||
content = result.content[0]
|
||||
assert isinstance(content, TextContent)
|
||||
assert "Unknown tool" in content.text
|
||||
|
|
@ -762,7 +763,7 @@ class TestServerResources:
|
|||
assert resource.description == "get_data returns a string"
|
||||
assert resource.uri == "function://test"
|
||||
assert resource.name == "test_get_data"
|
||||
assert resource.mimeType == "text/plain"
|
||||
assert resource.mime_type == "text/plain"
|
||||
|
||||
|
||||
class TestServerResourceTemplates:
|
||||
|
|
@ -892,8 +893,8 @@ class TestServerResourceTemplates:
|
|||
assert len(templates) == 1
|
||||
template = templates[0]
|
||||
|
||||
assert hasattr(template, "mimeType")
|
||||
assert template.mimeType == "text/csv"
|
||||
assert hasattr(template, "mime_type")
|
||||
assert template.mime_type == "text/csv"
|
||||
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
result = await client.read_resource("resource://bob/csv")
|
||||
|
|
@ -1382,7 +1383,7 @@ class TestServerPrompts:
|
|||
resource=TextResourceContents(
|
||||
uri="file://file.txt",
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -1397,7 +1398,7 @@ class TestServerPrompts:
|
|||
resource = content.resource
|
||||
assert isinstance(resource, TextResourceContents)
|
||||
assert resource.text == "File contents"
|
||||
assert resource.mimeType == "text/plain"
|
||||
assert resource.mime_type == "text/plain"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_unknown_prompt(self):
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ async def test_server_name_title_description_version():
|
|||
# Start server and connect client
|
||||
async with create_connected_server_and_client_session(mcp._mcp_server) as client:
|
||||
init_result = await client.initialize()
|
||||
assert init_result.serverInfo.name == "TestServer"
|
||||
assert init_result.serverInfo.title == "Test Server Title"
|
||||
assert init_result.serverInfo.description == "This is a test server description."
|
||||
assert init_result.serverInfo.version == "1.0"
|
||||
assert init_result.server_info.name == "TestServer"
|
||||
assert init_result.server_info.title == "Test Server Title"
|
||||
assert init_result.server_info.description == "This is a test server description."
|
||||
assert init_result.server_info.version == "1.0"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
|
@ -184,7 +184,7 @@ async def test_resource_title():
|
|||
|
||||
# List resource templates
|
||||
templates_result = await client.list_resource_templates()
|
||||
templates = {tpl.uriTemplate: tpl for tpl in templates_result.resourceTemplates}
|
||||
templates = {tpl.uri_template: tpl for tpl in templates_result.resource_templates}
|
||||
|
||||
# Verify dynamic resource template
|
||||
assert "resource://dynamic/{id}" in templates
|
||||
|
|
@ -203,17 +203,17 @@ async def test_get_display_name_utility():
|
|||
"""Test the get_display_name utility function."""
|
||||
|
||||
# Test tool precedence: title > annotations.title > name
|
||||
tool_name_only = Tool(name="test_tool", inputSchema={})
|
||||
tool_name_only = Tool(name="test_tool", input_schema={})
|
||||
assert get_display_name(tool_name_only) == "test_tool"
|
||||
|
||||
tool_with_title = Tool(name="test_tool", title="Test Tool", inputSchema={})
|
||||
tool_with_title = Tool(name="test_tool", title="Test Tool", input_schema={})
|
||||
assert get_display_name(tool_with_title) == "Test Tool"
|
||||
|
||||
tool_with_annotations = Tool(name="test_tool", inputSchema={}, annotations=ToolAnnotations(title="Annotated Tool"))
|
||||
tool_with_annotations = Tool(name="test_tool", input_schema={}, annotations=ToolAnnotations(title="Annotated Tool"))
|
||||
assert get_display_name(tool_with_annotations) == "Annotated Tool"
|
||||
|
||||
tool_with_both = Tool(
|
||||
name="test_tool", title="Primary Title", inputSchema={}, annotations=ToolAnnotations(title="Secondary Title")
|
||||
name="test_tool", title="Primary Title", input_schema={}, annotations=ToolAnnotations(title="Secondary Title")
|
||||
)
|
||||
assert get_display_name(tool_with_both) == "Primary Title"
|
||||
|
||||
|
|
@ -230,8 +230,8 @@ async def test_get_display_name_utility():
|
|||
prompt_with_title = Prompt(name="test_prompt", title="Test Prompt")
|
||||
assert get_display_name(prompt_with_title) == "Test Prompt"
|
||||
|
||||
template = ResourceTemplate(uriTemplate="file://{id}", name="test_template")
|
||||
template = ResourceTemplate(uri_template="file://{id}", name="test_template")
|
||||
assert get_display_name(template) == "test_template"
|
||||
|
||||
template_with_title = ResourceTemplate(uriTemplate="file://{id}", name="test_template", title="Test Template")
|
||||
template_with_title = ResourceTemplate(uri_template="file://{id}", name="test_template", title="Test Template")
|
||||
assert get_display_name(template_with_title) == "Test Template"
|
||||
|
|
|
|||
|
|
@ -426,8 +426,8 @@ class TestToolAnnotations:
|
|||
|
||||
annotations = ToolAnnotations(
|
||||
title="File Reader",
|
||||
readOnlyHint=True,
|
||||
openWorldHint=False,
|
||||
read_only_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
|
||||
manager = ToolManager()
|
||||
|
|
@ -435,8 +435,8 @@ class TestToolAnnotations:
|
|||
|
||||
assert tool.annotations is not None
|
||||
assert tool.annotations.title == "File Reader"
|
||||
assert tool.annotations.readOnlyHint is True
|
||||
assert tool.annotations.openWorldHint is False
|
||||
assert tool.annotations.read_only_hint is True
|
||||
assert tool.annotations.open_world_hint is False
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_annotations_in_fastmcp(self):
|
||||
|
|
@ -444,7 +444,7 @@ class TestToolAnnotations:
|
|||
|
||||
app = FastMCP()
|
||||
|
||||
@app.tool(annotations=ToolAnnotations(title="Echo Tool", readOnlyHint=True))
|
||||
@app.tool(annotations=ToolAnnotations(title="Echo Tool", read_only_hint=True))
|
||||
def echo(message: str) -> str: # pragma: no cover
|
||||
"""Echo a message back."""
|
||||
return message
|
||||
|
|
@ -453,7 +453,7 @@ class TestToolAnnotations:
|
|||
assert len(tools) == 1
|
||||
assert tools[0].annotations is not None
|
||||
assert tools[0].annotations.title == "Echo Tool"
|
||||
assert tools[0].annotations.readOnlyHint is True
|
||||
assert tools[0].annotations.read_only_hint is True
|
||||
|
||||
|
||||
class TestStructuredOutput:
|
||||
|
|
@ -794,7 +794,7 @@ class TestToolMetadata:
|
|||
app = FastMCP()
|
||||
|
||||
metadata = {"custom": "value"}
|
||||
annotations = ToolAnnotations(title="Combined Tool", readOnlyHint=True)
|
||||
annotations = ToolAnnotations(title="Combined Tool", read_only_hint=True)
|
||||
|
||||
@app.tool(meta=metadata, annotations=annotations)
|
||||
def combined_tool(data: str) -> str: # pragma: no cover
|
||||
|
|
@ -806,7 +806,7 @@ class TestToolMetadata:
|
|||
assert tools[0].meta == metadata
|
||||
assert tools[0].annotations is not None
|
||||
assert tools[0].annotations.title == "Combined Tool"
|
||||
assert tools[0].annotations.readOnlyHint is True
|
||||
assert tools[0].annotations.read_only_hint is True
|
||||
|
||||
|
||||
class TestRemoveTools:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ async def test_url_elicitation_accept():
|
|||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
assert params.url == "https://example.com/api_key_setup"
|
||||
assert params.elicitationId == "test-elicitation-001"
|
||||
assert params.elicitation_id == "test-elicitation-001"
|
||||
assert params.message == "Please provide your API key to continue."
|
||||
return ElicitResult(action="accept")
|
||||
|
||||
|
|
@ -160,9 +160,9 @@ async def test_url_no_content_in_response():
|
|||
# Verify that this is URL mode
|
||||
assert params.mode == "url"
|
||||
assert isinstance(params, types.ElicitRequestURLParams)
|
||||
# URL params have url and elicitationId, not requestedSchema
|
||||
# URL params have url and elicitation_id, not requested_schema
|
||||
assert params.url == "https://example.com/test"
|
||||
assert params.elicitationId == "test-001"
|
||||
assert params.elicitation_id == "test-001"
|
||||
# Return without content - this is correct for URL mode
|
||||
return ElicitResult(action="accept")
|
||||
|
||||
|
|
@ -199,8 +199,8 @@ async def test_form_mode_still_works():
|
|||
# Verify form mode parameters
|
||||
assert params.mode == "form"
|
||||
assert isinstance(params, types.ElicitRequestFormParams)
|
||||
# Form params have requestedSchema, not url/elicitationId
|
||||
assert params.requestedSchema is not None
|
||||
# Form params have requested_schema, not url/elicitation_id
|
||||
assert params.requested_schema is not None
|
||||
return ElicitResult(action="accept", content={"name": "Alice"})
|
||||
|
||||
async with create_connected_server_and_client_session(
|
||||
|
|
@ -341,7 +341,7 @@ async def test_deprecated_elicit_method():
|
|||
# Use the deprecated elicit() method which should call elicit_form()
|
||||
result = await ctx.session.elicit(
|
||||
message="Enter your email",
|
||||
requestedSchema=EmailSchema.model_json_schema(),
|
||||
requested_schema=EmailSchema.model_json_schema(),
|
||||
)
|
||||
|
||||
if result.action == "accept" and result.content:
|
||||
|
|
@ -351,7 +351,7 @@ async def test_deprecated_elicit_method():
|
|||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
# Verify this is form mode
|
||||
assert params.mode == "form"
|
||||
assert params.requestedSchema is not None
|
||||
assert params.requested_schema is not None
|
||||
return ElicitResult(action="accept", content={"email": "test@example.com"})
|
||||
|
||||
async with create_connected_server_and_client_session(
|
||||
|
|
@ -382,7 +382,7 @@ async def test_ctx_elicit_url_convenience_method():
|
|||
|
||||
async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
|
||||
assert params.mode == "url"
|
||||
assert params.elicitationId == "ctx-test-001"
|
||||
assert params.elicitation_id == "ctx-test-001"
|
||||
return ElicitResult(action="accept")
|
||||
|
||||
async with create_connected_server_and_client_session(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ async def test_url_elicitation_error_thrown_from_tool():
|
|||
mode="url",
|
||||
message=f"Authorization required to connect to {service_name}",
|
||||
url=f"https://{service_name}.example.com/oauth/authorize",
|
||||
elicitationId=f"{service_name}-auth-001",
|
||||
elicitation_id=f"{service_name}-auth-001",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -63,13 +63,13 @@ async def test_url_elicitation_error_from_error():
|
|||
mode="url",
|
||||
message="GitHub authorization required",
|
||||
url="https://github.example.com/oauth",
|
||||
elicitationId="github-auth",
|
||||
elicitation_id="github-auth",
|
||||
),
|
||||
types.ElicitRequestURLParams(
|
||||
mode="url",
|
||||
message="Google Drive authorization required",
|
||||
url="https://drive.google.com/oauth",
|
||||
elicitationId="gdrive-auth",
|
||||
elicitation_id="gdrive-auth",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
@ -89,13 +89,13 @@ async def test_url_elicitation_error_from_error():
|
|||
|
||||
# Verify the reconstructed error has both elicitations
|
||||
assert len(url_error.elicitations) == 2
|
||||
assert url_error.elicitations[0].elicitationId == "github-auth"
|
||||
assert url_error.elicitations[1].elicitationId == "gdrive-auth"
|
||||
assert url_error.elicitations[0].elicitation_id == "github-auth"
|
||||
assert url_error.elicitations[1].elicitation_id == "gdrive-auth"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_normal_exceptions_still_return_error_result():
|
||||
"""Test that normal exceptions still return CallToolResult with isError=True."""
|
||||
"""Test that normal exceptions still return CallToolResult with is_error=True."""
|
||||
mcp = FastMCP(name="NormalErrorServer")
|
||||
|
||||
@mcp.tool(description="A tool that raises a normal exception")
|
||||
|
|
@ -107,7 +107,7 @@ async def test_normal_exceptions_still_return_error_result():
|
|||
|
||||
# Normal exceptions should be returned as error results, not McpError
|
||||
result = await client_session.call_tool("failing_tool", {})
|
||||
assert result.isError is True
|
||||
assert result.is_error is True
|
||||
assert len(result.content) == 1
|
||||
assert isinstance(result.content[0], types.TextContent)
|
||||
assert "Something went wrong" in result.content[0].text
|
||||
|
|
|
|||
Loading…
Reference in a new issue