Skip to content

Exercise the SEP-2575 stateless probes and SEP-2243 resource/prompt headers in the conformance fixtures - #3442

Merged
maxisbey merged 2 commits into
mainfrom
conformance-fixture-depth
Sep 4, 2026
Merged

Exercise the SEP-2575 stateless probes and SEP-2243 resource/prompt headers in the conformance fixtures#3442
maxisbey merged 2 commits into
mainfrom
conformance-fixture-depth

Conversation

@maxisbey

@maxisbey maxisbey commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Two conformance scenarios were green against this SDK without measuring anything. This gives the everything-server the two diagnostic tools server-stateless probes by name, makes the conformance client's http-standard-headers handler round-trip a resource and a prompt as well as a tool, and (separate commit) renames examples/clients/simple-chatbot/README.MD to the README.md its pyproject declares so the workspace builds with --all-packages on case-sensitive filesystems.

Motivation and Context

server-stateless. Its sep-2575-http-server-no-independent-requests-on-stream and sep-2575-server-no-log-without-loglevel checks call tools named test_streaming_elicitation and test_logging_tool (stateless.ts). Our fixture had neither. MCPServer answers an unknown tool with an isError: true CallToolResult — a result frame — so the harness's not-testable guard (which only fires when every frame is a JSON-RPC error) never tripped, and both checks passed on "Unknown tool". typescript-sdk's fixture carries the pair (everythingServer.ts).

Rather than a tool that just returns text (which is byte-identical on the wire to the unknown-tool result), test_streaming_elicitation is a tool that genuinely needs elicitation, asked through a Resolve(...) dependency. On 2026-07-28 the SDK returns the question as an InputRequiredResult instead of putting elicitation/create on the response stream — which is the behaviour the check enforces — on 2025-11-25 it is a mid-call elicitation, and with no declared elicitation capability it is rejected with -32021. So the probe now sits on the SDK's version branch rather than beside it. test_logging_tool logs once on the request-scoped channel, so what the second check observes is the per-request logLevel gate.

http-standard-headers. The scenario reports any of tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get the client never sends as SKIPPED rather than failed. Our handler only listed and called a tool, so the Mcp-Method / Mcp-Name checks for resources and prompts were never judged. It now also reads the first resource and gets the first prompt.

README case. hatchling validates project.readme before building any target, so uv sync --all-packages / uv build --all-packages failed on Linux with OSError: Readme file does not exist: README.md. The conformance runner's built-in python-sdk config works around exactly this with two targeted syncs (known-sdks.ts).

How Has This Been Tested?

Against conformance 0.2.0-alpha.11 locally, through run-server.sh / run-client.sh:

  • server-stateless at --spec-version 2026-07-28: 30/30, both SEP-2575 checks SUCCESS on real results. Direct POSTs to the 2026-07-28 entry: test_streaming_elicitation with elicitation declared → 200, single application/json result with resultType: "input_required" and the elicitation/create embedded in inputRequests; with empty capabilities → 400 -32021 with requiredCapabilities.elicitation. test_logging_tool emits no notifications/message without _meta["io.modelcontextprotocol/logLevel"], exactly one with logLevel: "debug". In-memory Client(mode="legacy") and Client(mode="auto") both complete the tool via the elicitation callback.
  • http-standard-headers at 2026-07-28: 3 SUCCESS / 8 SKIPPED → 9 SUCCESS / 2 SKIPPED (the remaining two are initialize / notifications/initialized, which a 2026-07-28 client never sends).
  • All six CI legs: server 84/0 · 151/0 · 204/25 (the nine tasks-*, expected); client 224/0 · 387/0 · 464/9 (the nine per-check DPoP/WIF, expected). The client totals are +6 each versus main from the header checks moving out of SKIPPED; no baseline changes.
  • uv build --package mcp-simple-chatbot fails before the rename with the OSError above and succeeds after. ruff, pyright, and the markdownlint hook on the renamed file are clean.

Breaking Changes

None. Conformance fixtures and an example's file name only.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • test_logging_tool uses the deprecated request-scoped ctx.info (SEP-2577) on purpose — there is no non-deprecated channel and the check exists to exercise that gate — with the same reportDeprecated suppression the file already carries for test_tool_with_logging and test_reconnection.
  • The reason these checks went vacuous rather than "not testable" is that MCPServer reports an unknown tool as an isError result instead of a JSON-RPC error; any future name-probed harness fixture we lack will be masked the same way. Not changed here.
  • Once this lands the conformance repo's python-sdk runner entry can go back to uv sync --frozen --all-extras --all-packages. v1.x carries the same README.MD, so the python-sdk-v1 entry's --all-packages build has the same problem there; the rename is its own commit in case a [v1.x] backport is wanted.
  • On a case-insensitive filesystem an existing checkout may keep showing the old casing until re-cloned; git tracks the new name either way.

AI Disclaimer

…ares

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
…eaders 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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/actions/conformance/client.py">

<violation number="1" location=".github/actions/conformance/client.py:319">
P3: The 2025-11-25 conformance leg uses `client_mode() == "legacy"`, so it does send `initialize` and `notifications/initialized`; limit this SKIPPED explanation to modern mode so the fixture documentation matches its actual requests.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The 2025-11-25 conformance leg uses client_mode() == "legacy", so it does send initialize and notifications/initialized; limit this SKIPPED explanation to modern mode so the fixture documentation matches its actual requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/actions/conformance/client.py, line 319:

<comment>The 2025-11-25 conformance leg uses `client_mode() == "legacy"`, so it does send `initialize` and `notifications/initialized`; limit this SKIPPED explanation to modern mode so the fixture documentation matches its actual requests.</comment>

<file context>
@@ -311,11 +311,24 @@ async def run_request_metadata(server_url: str) -> None:
+    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.
+    """
</file context>

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, self-contained change to conformance fixtures and an example filename, with no src/mcp changes.

What was reviewed: the broadened http-standard-headers handler in .github/actions/conformance/client.py (reuses the existing _stub_required_args helper and guards each round-trip on non-empty lists); the two new everything-server tools, checking that Elicit(message, schema) / Resolve(fn) / ElicitationResult[T].action match the SDK's resolver API and that UserResponse is already defined in the file; the # pyright: ignore[reportDeprecated] on ctx.info, which matches five existing suppressions in the same file for the same deliberately-exercised deprecated channel; and the README.MD→README.md rename, confirmed as a pure R100 rename matching the readme = "README.md" declared in the chatbot's pyproject.

Extended reasoning...

Overview

The PR touches three files and nothing under src/mcp: (1) .github/actions/conformance/client.py extends the http-standard-headers scenario handler to also read the first resource and get the first prompt so the SEP-2243 Mcp-Method/Mcp-Name checks for those methods stop being reported SKIPPED, replacing a hardcoded add_numbers call with the first surfaced tool plus the file's existing _stub_required_args helper; (2) examples/servers/everything-server/mcp_everything_server/server.py adds the two diagnostic tools (test_streaming_elicitation, test_logging_tool) that the conformance server-stateless scenario probes by name, so the SEP-2575 checks measure real behaviour instead of passing vacuously on an unknown-tool isError result; (3) a pure R100 rename of the simple-chatbot README to the casing its pyproject declares.

Security risks

None identified. No authentication, crypto, permission, or transport code is touched. The new tools run only in the example/conformance fixture server; test_streaming_elicitation requests elicitation through the documented Resolve/Elicit API, and test_logging_tool emits a single fixed log line. The client handler only issues standard list/read/get calls against a harness-controlled server. No credentials or secrets appear in the diff.

Level of scrutiny

Low-to-moderate is appropriate: the change is small (49 insertions), touches only CI fixtures and examples, and the exact code paths added are the ones CI's conformance workflow executes, so a regression would surface directly there. I verified the API usage statically against the SDK source: Elicit.__init__(message, schema) and Resolve(fn) in src/mcp/server/mcpserver/resolve.py match the fixture's usage, ElicitationResult members carry .action, UserResponse is defined earlier in the same server file, and Elicit/ElicitationResult/Resolve are all exported from mcp.server.mcpserver. The rename shows 100% similarity and the pyproject indeed declares readme = "README.md".

Other factors

The added # pyright: ignore[reportDeprecated] on ctx.info mirrors five identical suppressions already in the same file for the same deprecated request-scoped channel, and the tool exists precisely to exercise that gate, so it follows the established local pattern rather than introducing a new suppression style. There is no CODEOWNERS file, the bug hunt exited on dry_streak with no findings, and the timeline shows no other reviewer activity or outstanding objections. The one behaviour I could not run locally (conformance results, pyright/ruff) is exactly what the conformance and lint CI legs cover, and the changed code is fixture-only, so a wrong assumption there fails CI rather than shipping to users.

@maxisbey
maxisbey merged commit 10dc173 into main Sep 4, 2026
37 checks passed
@maxisbey
maxisbey deleted the conformance-fixture-depth branch September 4, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant