This commit is contained in:
parent
3a6f2996cd
commit
837ef904f8
5 changed files with 1757 additions and 567 deletions
|
|
@ -20,6 +20,7 @@ from mcp import MCPError
|
|||
from mcp.client import ClientRequestContext
|
||||
from mcp.server.elicitation import AcceptedElicitation
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._helpers import IncomingMessage
|
||||
from tests.interaction._requirements import requirement
|
||||
|
|
@ -68,7 +69,7 @@ async def test_context_logging_helpers_send_log_notifications(connect: Connect)
|
|||
|
||||
|
||||
@requirement("mcpserver:context:progress")
|
||||
async def test_context_report_progress_sends_progress_notifications(connect: Connect) -> None:
|
||||
async def test_context_report_progress_sends_progress_notifications(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Context.report_progress sends progress notifications correlated to the calling request.
|
||||
|
||||
The caller's progress callback receives each report, in order, before the tool call returns.
|
||||
|
|
@ -88,7 +89,7 @@ async def test_context_report_progress_sends_progress_notifications(connect: Con
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("crunch", {}, progress_callback=on_progress)
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="crunched")], structured_content={"result": "crunched"})
|
||||
)
|
||||
assert received == snapshot([(1.0, 3.0, None), (2.0, 3.0, "halfway there")])
|
||||
|
|
@ -123,7 +124,7 @@ async def test_context_exposes_request_id_and_client_info_to_a_tool(connect: Con
|
|||
|
||||
@requirement("mcpserver:context:logging")
|
||||
@requirement("protocol:progress:no-token")
|
||||
async def test_report_progress_without_a_progress_token_sends_nothing(connect: Connect) -> None:
|
||||
async def test_report_progress_without_a_progress_token_sends_nothing(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""When the caller supplied no progress callback, Context.report_progress is a silent no-op.
|
||||
|
||||
The tool also emits one log message as a sentinel: the message handler receives only that,
|
||||
|
|
@ -145,7 +146,7 @@ async def test_report_progress_without_a_progress_token_sends_nothing(connect: C
|
|||
async with connect(mcp, message_handler=collect) as client:
|
||||
result = await client.call_tool("mill", {})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="milled")], structured_content={"result": "milled"})
|
||||
)
|
||||
assert received == snapshot(
|
||||
|
|
@ -207,7 +208,7 @@ async def test_context_elicit_returns_typed_result(connect: Connect) -> None:
|
|||
|
||||
|
||||
@requirement("mcpserver:context:read-resource")
|
||||
async def test_context_read_resource_reads_registered_resource(connect: Connect) -> None:
|
||||
async def test_context_read_resource_reads_registered_resource(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Context.read_resource lets a tool read a resource registered on the same server.
|
||||
|
||||
The tool reports the MIME type and content it read, proving the resource function ran and its
|
||||
|
|
@ -228,7 +229,7 @@ async def test_context_read_resource_reads_registered_resource(connect: Connect)
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("show_config", {})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(text="text/plain: 'theme = dark'")],
|
||||
structured_content={"result": "text/plain: 'theme = dark'"},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from mcp.client import ClaimContext, ClientExtension, ResultClaim, advertise
|
|||
from mcp.server.context import CallNext, HandlerResult, ServerRequestContext
|
||||
from mcp.server.extension import Extension
|
||||
from mcp.server.mcpserver import Context, MCPServer, require_client_extension
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
|
|
@ -79,7 +80,9 @@ def _receipt_shop(issuer: Extension) -> MCPServer:
|
|||
|
||||
|
||||
@requirement("extensions:client:claimed-result-resolved")
|
||||
async def test_claimed_result_is_finished_by_the_owning_extensions_resolver(connect: Connect) -> None:
|
||||
async def test_claimed_result_is_finished_by_the_owning_extensions_resolver(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""The owning extension's claim resolver redeems the substituted `receipt` through
|
||||
`ctx.session`, and `call_tool` returns the resolver's plain `CallToolResult`."""
|
||||
received: list[ReceiptResult] = []
|
||||
|
|
@ -92,7 +95,7 @@ async def test_claimed_result_is_finished_by_the_owning_extensions_resolver(conn
|
|||
result = await client.call_tool("buy", {"item": "lamp"})
|
||||
|
||||
assert [claimed.receipt_token for claimed in received] == ["r-117"]
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="goods for r-117")], structured_content={"result": "goods for r-117"})
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from mcp_types import (
|
|||
|
||||
from mcp import MCPError
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ pytestmark = pytest.mark.anyio
|
|||
|
||||
|
||||
@requirement("mcpserver:prompt:decorated")
|
||||
async def test_list_prompts_derives_arguments_from_signature(connect: Connect) -> None:
|
||||
async def test_list_prompts_derives_arguments_from_signature(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""A decorated prompt is listed with arguments derived from the function signature.
|
||||
|
||||
Parameters without a default are required; the description comes from the docstring.
|
||||
|
|
@ -36,7 +37,7 @@ async def test_list_prompts_derives_arguments_from_signature(connect: Connect) -
|
|||
async with connect(mcp) as client:
|
||||
result = await client.list_prompts()
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
ListPromptsResult(
|
||||
prompts=[
|
||||
Prompt(
|
||||
|
|
@ -53,7 +54,7 @@ async def test_list_prompts_derives_arguments_from_signature(connect: Connect) -
|
|||
|
||||
|
||||
@requirement("mcpserver:prompt:decorated")
|
||||
async def test_get_prompt_renders_function_return(connect: Connect) -> None:
|
||||
async def test_get_prompt_renders_function_return(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""The decorated function's string return value is rendered as a single user message."""
|
||||
mcp = MCPServer("prompter")
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ async def test_get_prompt_renders_function_return(connect: Connect) -> None:
|
|||
async with connect(mcp) as client:
|
||||
result = await client.get_prompt("greet", {"name": "Ada"})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
GetPromptResult(
|
||||
description="A personalised greeting.",
|
||||
messages=[PromptMessage(role="user", content=TextContent(text="Say hello to Ada."))],
|
||||
|
|
@ -141,7 +142,9 @@ async def test_get_prompt_with_a_wrong_type_argument_is_rejected_before_the_func
|
|||
|
||||
|
||||
@requirement("mcpserver:prompt:optional-args")
|
||||
async def test_get_prompt_with_an_optional_argument_omitted_uses_the_default(connect: Connect) -> None:
|
||||
async def test_get_prompt_with_an_optional_argument_omitted_uses_the_default(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""A prompt rendered without one of its optional arguments uses that parameter's default value."""
|
||||
mcp = MCPServer("prompter")
|
||||
|
||||
|
|
@ -153,7 +156,7 @@ async def test_get_prompt_with_an_optional_argument_omitted_uses_the_default(con
|
|||
async with connect(mcp) as client:
|
||||
result = await client.get_prompt("review", {"code": "x = 1"})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
GetPromptResult(
|
||||
description="Review a snippet of code against a style guide.",
|
||||
messages=[PromptMessage(role="user", content=TextContent(text="Review x = 1 per pep8."))],
|
||||
|
|
@ -162,7 +165,9 @@ async def test_get_prompt_with_an_optional_argument_omitted_uses_the_default(con
|
|||
|
||||
|
||||
@requirement("mcpserver:prompt:duplicate-name")
|
||||
async def test_registering_a_duplicate_prompt_name_warns_and_keeps_the_first(connect: Connect) -> None:
|
||||
async def test_registering_a_duplicate_prompt_name_warns_and_keeps_the_first(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""Registering a second prompt with an already-used name keeps the first registration.
|
||||
|
||||
The intended behaviour is rejection at registration time; MCPServer instead logs a warning
|
||||
|
|
@ -187,7 +192,7 @@ async def test_registering_a_duplicate_prompt_name_warns_and_keeps_the_first(con
|
|||
result = await client.get_prompt("greet")
|
||||
|
||||
assert [prompt.name for prompt in listed.prompts] == ["greet"]
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
GetPromptResult(
|
||||
description="The first registration; this is the one that wins.",
|
||||
messages=[PromptMessage(role="user", content=TextContent(text="first"))],
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from mcp_types import (
|
|||
|
||||
from mcp import MCPError
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ pytestmark = pytest.mark.anyio
|
|||
|
||||
|
||||
@requirement("mcpserver:resource:static")
|
||||
async def test_read_static_resource(connect: Connect) -> None:
|
||||
async def test_read_static_resource(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""A function registered for a fixed URI is served at that URI with its return value as text."""
|
||||
mcp = MCPServer("library")
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ async def test_read_static_resource(connect: Connect) -> None:
|
|||
async with connect(mcp) as client:
|
||||
result = await client.read_resource("config://app")
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
ReadResourceResult(
|
||||
contents=[TextResourceContents(uri="config://app", mime_type="text/plain", text="theme = dark")]
|
||||
)
|
||||
|
|
@ -41,7 +42,7 @@ async def test_read_static_resource(connect: Connect) -> None:
|
|||
|
||||
|
||||
@requirement("mcpserver:resource:static")
|
||||
async def test_list_static_and_templated_resources(connect: Connect) -> None:
|
||||
async def test_list_static_and_templated_resources(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Statically-registered resources appear in resources/list; templated ones only in templates/list.
|
||||
|
||||
The name and description are derived from the function name and docstring; the MIME type
|
||||
|
|
@ -63,7 +64,7 @@ async def test_list_static_and_templated_resources(connect: Connect) -> None:
|
|||
resources = await client.list_resources()
|
||||
templates = await client.list_resource_templates()
|
||||
|
||||
assert resources == snapshot(
|
||||
assert unstamped(resources) == snapshot(
|
||||
ListResourcesResult(
|
||||
resources=[
|
||||
Resource(
|
||||
|
|
@ -75,7 +76,7 @@ async def test_list_static_and_templated_resources(connect: Connect) -> None:
|
|||
]
|
||||
)
|
||||
)
|
||||
assert templates == snapshot(
|
||||
assert unstamped(templates) == snapshot(
|
||||
ListResourceTemplatesResult(
|
||||
resource_templates=[
|
||||
ResourceTemplate(
|
||||
|
|
@ -91,7 +92,7 @@ async def test_list_static_and_templated_resources(connect: Connect) -> None:
|
|||
|
||||
@requirement("mcpserver:resource:template")
|
||||
@requirement("resources:read:template-vars")
|
||||
async def test_read_templated_resource(connect: Connect) -> None:
|
||||
async def test_read_templated_resource(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Reading a URI that matches a registered template invokes the function with the extracted parameters."""
|
||||
mcp = MCPServer("library")
|
||||
|
||||
|
|
@ -103,7 +104,7 @@ async def test_read_templated_resource(connect: Connect) -> None:
|
|||
async with connect(mcp) as client:
|
||||
result = await client.read_resource("users://42/profile")
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
ReadResourceResult(
|
||||
contents=[TextResourceContents(uri="users://42/profile", mime_type="text/plain", text="profile for 42")]
|
||||
)
|
||||
|
|
@ -152,7 +153,9 @@ async def test_resource_function_that_raises_is_surfaced_as_a_jsonrpc_error(conn
|
|||
|
||||
|
||||
@requirement("mcpserver:resource:duplicate-name")
|
||||
async def test_registering_a_duplicate_resource_uri_warns_and_keeps_the_first(connect: Connect) -> None:
|
||||
async def test_registering_a_duplicate_resource_uri_warns_and_keeps_the_first(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""Registering a second static resource at an already-used URI keeps the first registration.
|
||||
|
||||
The intended behaviour is rejection at registration time; MCPServer instead logs a warning
|
||||
|
|
@ -178,6 +181,6 @@ async def test_registering_a_duplicate_resource_uri_warns_and_keeps_the_first(co
|
|||
|
||||
assert [resource.uri for resource in listed.resources] == ["config://app"]
|
||||
assert listed.resources[0].name == "config_first"
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
ReadResourceResult(contents=[TextResourceContents(uri="config://app", mime_type="text/plain", text="first")])
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from mcp import MCPError
|
|||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.server.mcpserver.exceptions import ToolError
|
||||
from mcp.shared.exceptions import UrlElicitationRequiredError
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._helpers import IncomingMessage
|
||||
from tests.interaction._requirements import requirement
|
||||
|
|
@ -28,7 +29,7 @@ pytestmark = pytest.mark.anyio
|
|||
|
||||
|
||||
@requirement("tools:call:content:text")
|
||||
async def test_call_tool_returns_text_content(connect: Connect) -> None:
|
||||
async def test_call_tool_returns_text_content(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Arguments reach the tool function; its return value comes back as text content.
|
||||
|
||||
MCPServer also derives an output schema from the return annotation and attaches the
|
||||
|
|
@ -43,11 +44,15 @@ async def test_call_tool_returns_text_content(connect: Connect) -> None:
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("add", {"a": 2, "b": 3})
|
||||
|
||||
assert result == snapshot(CallToolResult(content=[TextContent(text="5")], structured_content={"result": "5"}))
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="5")], structured_content={"result": "5"})
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:schema-variants")
|
||||
async def test_complex_parameter_types_are_validated_and_coerced_before_the_tool_runs(connect: Connect) -> None:
|
||||
async def test_complex_parameter_types_are_validated_and_coerced_before_the_tool_runs(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""Literal, nested-model, and constrained parameters are validated and coerced from the wire arguments.
|
||||
|
||||
The string "3" is coerced to `int` and the `point` dict to a `Point` instance before the function
|
||||
|
|
@ -67,7 +72,7 @@ async def test_complex_parameter_types_are_validated_and_coerced_before_the_tool
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("place", {"mode": "fast", "point": {"x": "3", "y": 4}, "count": 5})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(text="fast at (3, 4) x5")], structured_content={"result": "fast at (3, 4) x5"}
|
||||
)
|
||||
|
|
@ -76,7 +81,7 @@ async def test_complex_parameter_types_are_validated_and_coerced_before_the_tool
|
|||
|
||||
@requirement("mcpserver:tool:handler-throws")
|
||||
@requirement("mcpserver:output-schema:skip-on-error")
|
||||
async def test_call_tool_function_exception_becomes_error_result(connect: Connect) -> None:
|
||||
async def test_call_tool_function_exception_becomes_error_result(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""An exception raised by a tool function is returned as an is_error result, not a JSON-RPC error.
|
||||
|
||||
The function's `-> str` annotation gives the tool a derived output schema, but the error
|
||||
|
|
@ -92,13 +97,13 @@ async def test_call_tool_function_exception_becomes_error_result(connect: Connec
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("explode", {})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="Error executing tool explode: boom")], is_error=True)
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:handler-throws")
|
||||
async def test_call_tool_tool_error_becomes_error_result(connect: Connect) -> None:
|
||||
async def test_call_tool_tool_error_becomes_error_result(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""A ToolError raised by a tool function is returned as an is_error result, not a JSON-RPC error."""
|
||||
mcp = MCPServer("errors")
|
||||
|
||||
|
|
@ -109,13 +114,13 @@ async def test_call_tool_tool_error_becomes_error_result(connect: Connect) -> No
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("flux", {})
|
||||
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="Error executing tool flux: flux capacitor offline")], is_error=True)
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:unknown-name")
|
||||
async def test_call_tool_unknown_name_returns_error_result(connect: Connect) -> None:
|
||||
async def test_call_tool_unknown_name_returns_error_result(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""Calling a tool name that was never registered is reported as an is_error result.
|
||||
|
||||
The spec classifies unknown tools as a protocol error; see the divergence note on the
|
||||
|
|
@ -130,12 +135,14 @@ async def test_call_tool_unknown_name_returns_error_result(connect: Connect) ->
|
|||
async with connect(mcp) as client:
|
||||
result = await client.call_tool("nope", {})
|
||||
|
||||
assert result == snapshot(CallToolResult(content=[TextContent(text="Unknown tool: nope")], is_error=True))
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="Unknown tool: nope")], is_error=True)
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:output-schema:model")
|
||||
@requirement("tools:call:structured-content:text-mirror")
|
||||
async def test_call_tool_model_return_becomes_structured_content(connect: Connect) -> None:
|
||||
async def test_call_tool_model_return_becomes_structured_content(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""A tool returning a pydantic model advertises the model's schema as the tool's output schema
|
||||
and returns the model's fields as structured content alongside a serialised text block.
|
||||
"""
|
||||
|
|
@ -164,7 +171,7 @@ async def test_call_tool_model_return_becomes_structured_content(connect: Connec
|
|||
"type": "object",
|
||||
}
|
||||
)
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(
|
||||
content=[
|
||||
TextContent(
|
||||
|
|
@ -182,7 +189,7 @@ async def test_call_tool_model_return_becomes_structured_content(connect: Connec
|
|||
|
||||
|
||||
@requirement("mcpserver:tool:output-schema:wrapped")
|
||||
async def test_call_tool_list_return_is_wrapped_in_result_key(connect: Connect) -> None:
|
||||
async def test_call_tool_list_return_is_wrapped_in_result_key(connect: Connect, unstamped: Unstamp) -> None:
|
||||
"""A tool returning a list wraps the value under a "result" key in both the generated output
|
||||
schema and the structured content.
|
||||
"""
|
||||
|
|
@ -204,7 +211,7 @@ async def test_call_tool_list_return_is_wrapped_in_result_key(connect: Connect)
|
|||
"type": "object",
|
||||
}
|
||||
)
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(text="2"), TextContent(text="3"), TextContent(text="5")],
|
||||
structured_content={"result": [2, 3, 5]},
|
||||
|
|
@ -279,7 +286,9 @@ async def test_tool_with_output_schema_returning_mismatched_structured_content_i
|
|||
|
||||
|
||||
@requirement("mcpserver:tool:duplicate-name")
|
||||
async def test_registering_a_duplicate_tool_name_warns_and_keeps_the_first(connect: Connect) -> None:
|
||||
async def test_registering_a_duplicate_tool_name_warns_and_keeps_the_first(
|
||||
connect: Connect, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""Registering a second tool with an already-used name keeps the first registration.
|
||||
|
||||
The intended behaviour is rejection at registration time; MCPServer instead logs a warning
|
||||
|
|
@ -304,14 +313,14 @@ async def test_registering_a_duplicate_tool_name_warns_and_keeps_the_first(conne
|
|||
result = await client.call_tool("echo", {})
|
||||
|
||||
assert [tool.name for tool in listed.tools] == ["echo"]
|
||||
assert result == snapshot(
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="first")], structured_content={"result": "first"})
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:naming-validation")
|
||||
async def test_registering_a_tool_with_a_spec_invalid_name_warns_but_does_not_reject(
|
||||
connect: Connect, caplog: pytest.LogCaptureFixture
|
||||
connect: Connect, caplog: pytest.LogCaptureFixture, unstamped: Unstamp
|
||||
) -> None:
|
||||
"""A tool name that violates the SEP-986 rules logs a warning at registration but is still registered.
|
||||
|
||||
|
|
@ -340,7 +349,9 @@ async def test_registering_a_tool_with_a_spec_invalid_name_warns_but_does_not_re
|
|||
result = await client.call_tool("bad name!", {})
|
||||
|
||||
assert [tool.name for tool in listed.tools] == ["bad name!"]
|
||||
assert result == snapshot(CallToolResult(content=[TextContent(text="ok")], structured_content={"result": "ok"}))
|
||||
assert unstamped(result) == snapshot(
|
||||
CallToolResult(content=[TextContent(text="ok")], structured_content={"result": "ok"})
|
||||
)
|
||||
|
||||
|
||||
@requirement("mcpserver:tool:url-elicitation-error")
|
||||
|
|
|
|||
Loading…
Reference in a new issue