Skip to content

Model nested experimental values and extensions in Client.Capabilities - #276

Open
nstrm wants to merge 1 commit into
modelcontextprotocol:mainfrom
nstrm:fix/client-capabilities-experimental-extensions
Open

nstrm wants to merge 1 commit into
modelcontextprotocol:mainfrom
nstrm:fix/client-capabilities-experimental-extensions

Conversation

@nstrm

@nstrm nstrm commented Aug 19, 2026

Copy link
Copy Markdown

Fixes #262.

Client.Capabilities.experimental is typed [String: String]?, but the spec defines it as a map of arbitrary objects. Clients that send nested values there — ChatGPT sends {"openai/visibility": {"enabled": true}} — fail decoding inside TypedRequestHandler, and the server returns JSON-RPC -32603 to a valid initialize. The extensions capability map was also unmodeled (ignored on decode).

Note for reviewers: this changes the public type of experimental. Dictionary literals with string values still compile (Value is ExpressibleByStringLiteral); no call sites in the repo needed updating.

@dergachoff

Copy link
Copy Markdown

Second reproducer for #262: Codex CLI 0.154.0 (released 2026-09-09) sends "capabilities":{"experimental":{"codex/auth-change":{}}} on initialize, so every server on 0.12.1 fails the handshake with -32603. Rebuilt my server (Epistates/MCPSafari) against f7077e0 and the same request succeeds.

ianegordon added a commit to ianegordon/swift-sdk that referenced this pull request Sep 11, 2026
…nt.Capabilities (upstream modelcontextprotocol#276, nstrm)

Manifest entry 7. Upstream refs/pull/276/head at f7077e0, unmodified.

Client.Capabilities.experimental was typed [String: String], but the
2025-11-25 schema defines it as a map of arbitrary objects. A ChatGPT-shaped
initialize carrying {"openai/visibility": {"enabled": true}} failed to
decode and the server answered -32603 to a valid request. Reproduced before
the merge and confirmed fixed after.

experimental becomes [String: Value]; extensions is added as [String: Value].
Note that extensions is a draft-schema capability, absent from 2025-11-25 —
an unmodeled key already decoded fine, so that half is a feature, not a fix.

This is a source-breaking public type change: dictionary literals with string
values still compile, but a typed [String: String] variable and reading a
value back as String do not. A deprecated compatibility initializer follows
as entry 7a.

Fixes modelcontextprotocol#262.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eb9yGSXH1TVkg5Afsu9phk
ianegordon added a commit to ianegordon/swift-sdk that referenced this pull request Sep 11, 2026
modelcontextprotocol#276 changes Client.Capabilities.experimental from [String: String] to
[String: Value], which is source-breaking: a dictionary literal with string
values still compiles because Value is ExpressibleByStringLiteral, but a
typed [String: String] variable does not.

This restores that call site behind a deprecation warning. experimental is
deliberately non-optional and without a default: with every parameter
defaulted, overload resolution routes the bare Client.Capabilities() call to
this initializer and warns on it, which is noise unrelated to experimental.
Requiring the parameter keeps that call on the designated initializer.

Reading a value back as String is not restored — experimental is a stored
property and Swift has no second property of the same name, so consumers that
read values out still need updating.

Follows the deprecated compatibility factories in Sources/MCP/Server/Tools.swift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eb9yGSXH1TVkg5Afsu9phk
ianegordon added a commit to ianegordon/swift-sdk that referenced this pull request Sep 11, 2026
…nt.Capabilities

Manifest entry 7a. Fork addition at 604afd2, one clean commit stacked on
modelcontextprotocol#276's f7077e0, offerable upstream as submitted.

modelcontextprotocol#276's public type change is source-breaking for a typed [String: String]
variable. This restores that call site behind a deprecation warning, with
experimental required and undefaulted so the bare Client.Capabilities() call
stays on the designated initializer instead of warning. Reading a value back
as String is still unsupported and cannot be shimmed by an initializer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eb9yGSXH1TVkg5Afsu9phk
ianegordon added a commit to ianegordon/swift-sdk that referenced this pull request Sep 11, 2026
… as entry 7a

Client.Capabilities.experimental was [String: String] where the 2025-11-25
schema defines a map of arbitrary objects, so a ChatGPT-shaped initialize
carrying {"openai/visibility": {"enabled": true}} failed to decode and the
server answered -32603 to a valid request. Reproduced on the wire before the
merge and confirmed fixed after.

The entry records what the PR description understates: the type change is
source-breaking for a typed [String: String] variable and for reading a value
back as String, and the extensions field it adds is a draft-schema capability
absent from 2025-11-25 — an unmodeled key already decoded fine, so that half
is a feature rather than a fix.

7a restores the typed-variable call site behind a deprecation warning. The
read direction cannot be shimmed by an initializer and still needs updating
downstream.

Remaining candidate modelcontextprotocol#278 renumbered 7 to 6.

Tracking: fork issue #11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eb9yGSXH1TVkg5Afsu9phk
ianegordon added a commit to ianegordon/swift-sdk that referenced this pull request Sep 11, 2026
First fork tag carrying a library change since .5: modelcontextprotocol#276 fixes the -32603 a
server returns to a ChatGPT-shaped initialize, and 7a adds the deprecated
[String: String] compatibility initializer. Tags .5 and .6 differ only in the
conformance harness, so this is the first tag downstream has a reason to move
to since .5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eb9yGSXH1TVkg5Afsu9phk
steipete added a commit to openclaw/Peekaboo that referenced this pull request Sep 24, 2026
Adopt the single Swift MCP SDK decoder repair at f7077e0d5cd57e0b2a497862017aa94ee344252f, directly atop 0.12.1, so structured experimental capabilities can initialize and reach the tool catalog. Remove Peekaboo's overwritten initialize handler and keep negotiation owned by the SDK.

Preserve rejection of malformed known capabilities, add raw-wire and built-CLI stdio regression coverage, and document the typed string-dictionary mapValues(Value.string) migration and temporary pin's tagged-release follow-up. Upstream repair: modelcontextprotocol/swift-sdk#276 by @nstrm.

Also replace an existing watch-stop fixture's whole-session 80 ms stopwatch, which included artifact finalization, with observed fallback-runner cancellation and exact attempt-count evidence. Production capture behavior and watchdog/timeout values remain unchanged.

Final-head macOS, supplemental full suites, and CodeQL passed. Independent review found no actionable defects; local Core, CLI, consumer type-check, and signed concurrent stdio proof are recorded in the PR.

Fixes #745. Thanks @Wudib for the report.
@varun06

varun06 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Hi @nstrm, thanks for this PR! I've reviewed it and it looks good to merge.

Could you please rebase it onto the latest main? CI was broken on macOS runners for every PR (a toolchain/SDK mismatch, fixed in #306), so rebasing will pick up the fix and give us a green run. I'll review and merge once it's updated.

This pairs with #289 (the server side of the same change); I plan to merge them together so both use Value for experimental/extensions.

If you're no longer able to work on this, no problem: just let us know. If we don't hear back in about two weeks, we may carry the change forward in a new PR, with credit to you.

Type experimental as [String: Value] per the spec and add the
extensions capability map, so initialize requests carrying nested
JSON (e.g. ChatGPT's) decode instead of failing with -32603.

Fixes modelcontextprotocol#262
@nstrm
nstrm force-pushed the fix/client-capabilities-experimental-extensions branch from f7077e0 to 6f02d8e Compare October 22, 2026 18:31
@nstrm

nstrm commented Oct 22, 2026

Copy link
Copy Markdown
Author

Thanks for the review, @varun06! Rebased onto the latest main (now on top of #306), no conflicts, still a single commit. Happy to adjust anything if this needs aligning with #289 on the Value shape; otherwise it should be ready for a green run. Still around and glad to carry it through.

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.

Server fails to decode ChatGPT's initialize (experimental typed [String: String], extensions unmodeled); error is returned inside an HTTP 200

3 participants