From c55d7f731c31906ac0dbd37523402ba87e0efe3d Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:29:44 +0000 Subject: [PATCH 1/2] Rename simple-chatbot's README.MD to the README.md its pyproject declares hatchling validates project.readme before building any target, so on a case-sensitive filesystem `uv sync --all-packages` / `uv build --all-packages` failed for the whole workspace with "Readme file does not exist: README.md". The conformance runner's built-in python-sdk config works around exactly this with two targeted syncs. No-Verification-Needed: example file rename only --- examples/clients/simple-chatbot/{README.MD => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/clients/simple-chatbot/{README.MD => README.md} (100%) 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 From 4f754765a4a21b8e328999d962c1a91d067f5da2 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:29:45 +0000 Subject: [PATCH 2/2] Exercise the SEP-2575 stateless probes and SEP-2243 resource/prompt headers in the conformance fixtures Two conformance scenarios were green against this SDK without measuring anything. server-stateless probes tools named test_streaming_elicitation and test_logging_tool for its no-independent-requests-on-stream and no-log-without-logLevel checks. The everything-server had neither, and MCPServer answers an unknown tool with an isError result rather than a JSON-RPC error, so the harness's not-testable guard never fired and both checks passed on "Unknown tool". test_streaming_elicitation is now a tool that genuinely needs elicitation, asked through a Resolve() dependency: on 2026-07-28 the question comes back as an InputRequiredResult instead of an elicitation/create on the response stream (the behaviour the check enforces), on 2025-11-25 it is a mid-call elicitation, and without a declared elicitation capability it is rejected with -32021. test_logging_tool logs once on the request-scoped channel so the per-request logLevel gate is what the check observes. The client's http-standard-headers handler only listed and called a tool, so the Mcp-Method/Mcp-Name checks for resources/list, resources/read, prompts/list and prompts/get were reported SKIPPED. It now reads the first resource and gets the first prompt as well (3 -> 9 checks judged; initialize/notifications/initialized stay SKIPPED because the modern path never sends them). No-Verification-Needed: conformance fixtures only --- .github/actions/conformance/client.py | 23 ++++++++++--- .../mcp_everything_server/server.py | 32 ++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) 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/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"]}