Skip to content

[BUG]: fast-time-server strict 2026-07-28 mode is not interoperable with mcp SDK 2.0.0b2 (DiscoverResult.serverInfo placement + missing cacheScope/ttlMs) #11

Description

@jonpspri

First off: the move to a genuinely strict, modern-only mode in the latest cfex-mcp-fast-time-server image is a great step — having a reference server that speaks only 2026-07-28 (stateless, SEP-2575) is exactly what's needed to conformance-test modern MCP clients, and the /version endpoint now advertising mcp_versions: ["2026-07-28"] makes that intent unambiguous.

While testing ContextForge's migrated federation path (mcp.client.Client, mcp==2.0.0b2 / mcp_types wire models) against that image, I ran into two wire-shape mismatches between the server's modern responses and the SDK's 2026-07-28 models. Individually small, but together they mean no negotiation mode can currently connect to the strict image: auto, legacy, and an explicit 2026-07-28 pin all fail.

Environment

  • Image: ghcr.io/ibm/cfex-mcp-fast-time-server:latest @ sha256:2f085782c3aeb0dc149e91f0e6a1326f1b14cababdd4a780478a09fbb5117824 (pulled 2026-07-22), run as --protocol 2026-07-28 --strict
  • Client: mcp==2.0.0b2 with the standalone mcp_types package (mcp_types/v2026_07_28 wire models)
  • Context: ContextForge gateway (mcp-context-forge 2.0.0 prerelease) federating to the server as an upstream, but everything below reproduces with the bare SDK.

Behavior matrix (verified by curl today)

Probe Result
server/discover with namespaced _meta (io.modelcontextprotocol/protocolVersion, …) HTTP 200, DiscoverResult (see Gap 1)
initialize proposing 2025-11-25 JSON-RPC -32602 "Unsupported protocol version", data.supported: ["2026-07-28"]
initialize proposing 2026-07-28 JSON-RPC -32602 (same body) — expected, modern mode is handshake-less

The legacy-initialize removal itself looks intentional (and welcome for strictness) — the issue is what remains on the modern path.

Gap 1 — DiscoverResult.serverInfo is nested under _meta instead of top-level

The server's server/discover response currently looks like:

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "fast-time-server", "version": "0.1.0" } },
    "capabilities": { "tools": {} },
    "instructions": "Ultra-fast MCP test server.",
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"]
  }
}

But mcp_types.DiscoverResult requires server_info (alias serverInfo) as a required top-level field of the result object (mcp_types/_types.py, class DiscoverResult(CacheableResult)server_info: Implementation, alongside supported_versions and capabilities). Because serverInfo is absent at the top level, SDK-side validation fails:

pydantic.ValidationError: serverInfo — Field required

Impact on Client(mode="auto"): negotiate_auto() treats an unparseable discover result as "not modern evidence" and falls back to the legacy initialize handshake at 2025-11-25. In the previous image that silently "worked" (the legacy path was still accepted, masking the problem). Now that legacy initialize is rejected (-32602), the fallback fails too, so auto mode cannot connect at all:

MCPError: Unsupported protocol version   (from the legacy fallback initialize)

Suggested shape (matches the SDK model):

{
  "result": {
    "serverInfo": { "name": "fast-time-server", "version": "0.1.0" },
    "capabilities": { "tools": {} },
    "instructions": "Ultra-fast MCP test server.",
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"]
  }
}

(If keeping serverInfo in _meta is deliberate per a newer SEP revision, it'd be worth confirming against the mcp_types maintainers — but as of 2.0.0b2 the wire model expects it top-level.)

Gap 2 — modern tools/list responses omit required cacheScope / ttlMs

With an explicit Client(mode="2026-07-28") pin (which skips the probe and adopts the version directly), the handshake succeeds, but tools/list then fails validation:

pydantic.ValidationError: 2 validation errors for ListToolsResult
  cacheScope — Field required
  ttlMs      — Field required

In the mcp_types/v2026_07_28 wire models, CacheableResult fields are required and defaultless: cache_scope: Literal["private", "public"] (alias cacheScope) and ttl_ms: int >= 0 (alias ttlMs) — the docstring notes "Both fields are required on the 2026-07-28 wire." The server's modern list results currently include neither, so the pinned mode can't complete a tools/list.

A conservative fix that matches the SDK's own defaulting behavior: emit "cacheScope": "private", "ttlMs": 0 on modern list results (immediately stale, no shared caching).

Reproduction

# Gap 1: discover succeeds at the wire but serverInfo is in the wrong place
curl -s -X POST http://localhost:9080/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Protocol-Version: 2026-07-28' -H 'MCP-Method: server/discover' \
  -d '{"jsonrpc":"2.0","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"probe","version":"1.0"},"io.modelcontextprotocol/clientCapabilities":{}}},"id":1}'

# All three SDK modes fail
python - <<'EOF'
import asyncio
from mcp import Client

async def probe(mode):
    try:
        async with Client("http://localhost:9080/mcp", mode=mode) as c:
            await c.list_tools()
            print(f"{mode}: OK")
    except Exception as e:
        print(f"{mode}: FAIL")

for m in ["auto", "legacy", "2026-07-28"]:
    asyncio.run(probe(m))
EOF

Impact

Any SDK-2.0.0b2 client — ContextForge's federation path included — currently cannot register or call the strict image in any mode. With Gap 1 fixed, auto mode should negotiate 2026-07-28 end-to-end; with Gap 2 fixed, explicit version pins and modern catalog sync should complete. Happy to re-run our full negotiation E2E matrix (auto/legacy/pin, wire-level captures) against a patched image and report back.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions