diff --git a/.github/actions/conformance/client.py b/.github/actions/conformance/client.py index 18e59a8ac6..daad27697c 100644 --- a/.github/actions/conformance/client.py +++ b/.github/actions/conformance/client.py @@ -20,7 +20,7 @@ json-schema-ref-no-deref - Connect, list tools (no $ref deref) json-schema-2020-12-preservation - List tools, echo the focal inputSchema back verbatim request-metadata - Connect with all callbacks; client stamps _meta - http-standard-headers - Connect, call a tool (Mcp-* headers checked) + http-standard-headers - Tool, resource and prompt round-trips (Mcp-* headers checked) http-invalid-tool-headers - List tools, call every surfaced tool (x-mcp-header filter) http-custom-headers - Replay the harness's toolCalls (x-mcp-header -> Mcp-Param-*) elicitation-sep1034-client-defaults - Elicitation with default accept callback @@ -311,11 +311,24 @@ async def run_request_metadata(server_url: str) -> None: @register("http-standard-headers") async def run_http_standard_headers(server_url: str) -> None: - """Connect on the modern path so Mcp-Method / Mcp-Name / MCP-Protocol-Version are sent (SEP-2243).""" + """Touch tools, resources and prompts on the modern path so each standard header is checked (SEP-2243). + + The scenario inspects Mcp-Method on each request and Mcp-Name on tools/call, + resources/read and prompts/get, and reports methods the client never sent as + SKIPPED rather than failed, so exercise one of each. initialize and + notifications/initialized stay SKIPPED: the modern path discovers via + server/discover and never sends them. + """ async with Client(server_url, mode=client_mode()) as client: - await client.list_tools() - result = await client.call_tool("add_numbers", {"a": 5, "b": 3}) - logger.debug(f"add_numbers result: {result}") + tools = await client.list_tools() + if tools.tools: + await client.call_tool(tools.tools[0].name, _stub_required_args(tools.tools[0].input_schema)) + resources = await client.list_resources() + if resources.resources: + await client.read_resource(resources.resources[0].uri) + prompts = await client.list_prompts() + if prompts.prompts: + await client.get_prompt(prompts.prompts[0].name) def _stub_required_args(input_schema: dict[str, Any]) -> dict[str, Any]: diff --git a/examples/clients/simple-chatbot/README.MD b/examples/clients/simple-chatbot/README.md similarity index 100% rename from examples/clients/simple-chatbot/README.MD rename to examples/clients/simple-chatbot/README.md diff --git a/examples/servers/everything-server/mcp_everything_server/server.py b/examples/servers/everything-server/mcp_everything_server/server.py index 5f2194611c..836032a102 100644 --- a/examples/servers/everything-server/mcp_everything_server/server.py +++ b/examples/servers/everything-server/mcp_everything_server/server.py @@ -12,7 +12,7 @@ import click from mcp.server import ServerRequestContext -from mcp.server.mcpserver import Context, MCPServer, RequestStateSecurity +from mcp.server.mcpserver import Context, Elicit, ElicitationResult, MCPServer, RequestStateSecurity, Resolve from mcp.server.mcpserver.exceptions import ToolError from mcp.server.mcpserver.prompts.base import Prompt, UserMessage from mcp.server.streamable_http import EventCallback, EventMessage, EventStore @@ -350,6 +350,9 @@ def test_x_mcp_header( return f"region={region}" +# SEP-2575 server-stateless diagnostics (the conformance scenario probes these tools by name) + + @mcp.tool() async def test_missing_capability(ctx: Context) -> str: """Tests that a handler-raised MISSING_REQUIRED_CLIENT_CAPABILITY surfaces as a top-level JSON-RPC error. @@ -370,6 +373,33 @@ async def test_missing_capability(ctx: Context) -> str: return "Client declared sampling capability; proceeding." +def _ask_stream_probe() -> Elicit[UserResponse]: + return Elicit("The stateless streaming probe asks for a word", UserResponse) + + +@mcp.tool() +async def test_streaming_elicitation( + answer: Annotated[ElicitationResult[UserResponse], Resolve(_ask_stream_probe)], +) -> str: + """A tool that needs elicitation, asked through a resolver (SEP-2575 / SEP-2322). + + On 2026-07-28 the question is returned as an InputRequiredResult rather than sent + on the response stream; on earlier versions it is a mid-call elicitation request. + """ + return f"elicitation {answer.action}" + + +@mcp.tool() +async def test_logging_tool(ctx: Context) -> str: + """Logs once on the request-scoped channel (SEP-2575). + + On 2026-07-28 the message is delivered only when the request's `_meta` sets + `io.modelcontextprotocol/logLevel`. + """ + await ctx.info("test_logging_tool ran") # pyright: ignore[reportDeprecated] + return "logged through the request-scoped, logLevel-gated channel" + + # SEP-2322 InputRequiredResult fixtures (multi-round-trip / ephemeral workflow) NAME_SCHEMA = {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}