Skip to content

docs(mcp): document the text-only knob for OpenCode 2; fix the mcp.text_only description - #311

Merged
dondai44423 merged 2 commits into
dondai44423:masterfrom
mnaza:fix-opencode2-client-detection
Sep 25, 2026
Merged

dondai44423 merged 2 commits into
dondai44423:masterfrom
mnaza:fix-opencode2-client-detection

Conversation

@mnaza

@mnaza mnaza commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #306. Reworked from the original handshake-probe revision after review; compat.rs is no longer touched by this PR.

What ships

  1. mcp.text_only is described as what it does: fold structuredContent into a leading [meta] text block for every client. It never stripped image blocks, and web_screenshot returning its PNG with the knob on is correct.
  2. The README's client-compatibility block now documents the one client that needs the knob today: OpenCode 2.x reports clientInfo.name as cli (its process.env.OPENCODE_CLIENT ?? "cli" default, which serve never overrides), too generic to match in the text-only list, and its Code Mode shows the model structuredContent only. Set [mcp] text_only = true (or DONSETCH_MCP__TEXT_ONLY=1) for it until OpenCode restores a usable name upstream.

What was dropped, and why

The earlier revision recognized OpenCode 2 by handshake fingerprint (name cli, major version >= 2, elicitation.form.applyDefaults). applyDefaults is a TypeScript-SDK opt-in (SEP-1036), not an OpenCode signature: any SDK-based client can send it, later ones will, and the match would misfire silently while claiming a certainty the handshake does not carry. The compat layer's designed path for a newly-discovered broken host is the manual override (layer 3, "fixed today without waiting for a release"), and the name list stays name-based with its exit plan.

Upstream

The defect is OpenCode's: v2 regressed clientInfo.name, undoing the intent of anomalyco/opencode#2289, which hardcoded opencode precisely so MCP servers could identify the client.

Tests: the fingerprint and its test are removed; existing compat coverage is unchanged. Local: fmt + check + scoped nextest; CI is the gate.

…hake

OpenCode 2.x reports clientInfo.name as "cli": its default is
`process.env.OPENCODE_CLIENT ?? "cli"` and the serve service never
sets the variable, so every default install says cli (dondai44423#306). The
text-only client list from dondai44423#27 matched "opencode", so 2.x got the
split shape; with Code Mode on by default it keeps structuredContent
and drops content, and the model saw `{"content_ok": true, "url":
...}` and none of the page text.

"cli" alone is anyone's name, so the name list does not grow. The
2.x handshake is recognised by its fingerprint instead: a version
whose major is 2 or more together with the
`capabilities.elicitation.form.applyDefaults` field, which no other
client sends. A bare "cli" without it, the same capability on a 1.x
version, and opencode-web all keep the default split, as the
report's table has them.

Also: `mcp.text_only` was described as "strip image blocks from tool
output"; the fold never touched image blocks. It is described as
what it does now. README names OpenCode 2 among the clients that
show one surface and how it is detected.

Test: opencode_2_is_recognised_by_its_handshake_not_its_name (the
captured 2.0.16 initialize; red: left Default).

@Mart-Bogdan Mart-Bogdan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I firmly object to this change!

Name cli is too generaic, and there can be bunch of different software that have major version of 2.

This is:

  1. Upstream bug (OpenCode)
  2. We have configuration knob for this case.

Other way around can be to isntead default to text mode and enable dual mode for clients that we know are good, but with current setup I consider current implementation of probe harmful.

Better fix would be to just change README to add isntruction how to configure for such clietnts.

@Mart-Bogdan

Copy link
Copy Markdown
Collaborator

Regarding readme, I saw you edited readme a bit, we can instead expand it into proper section with better instruction how to enable this.

P.S. AFAIK only lients that benefit from dual mode are ones like OMP (perhaps Pi) who allows model to use code mode to inspect raw MCP response, so we might think on flipping default resonse mode and instead use knob/detection for clients that accept both fields. But that different story, and it doesn't change fact that OpenCode v2 sends bad "UserAgent" and detecting it by signature of how elicitation is implemented + major version is not so good ideal.

@Mart-Bogdan

Copy link
Copy Markdown
Collaborator

Some background on applyDefaults, since the PR treats it as an OpenCode fingerprint ("which no other client sends"). It is not one.

applyDefaults is not part of the MCP spec. In both the 2025-11-25 and 2026-07-28 schemas the client's elicitation capability is { form?: object; url?: object }, and form is an opaque object with no defined sub-fields. The key comes from the official TypeScript SDK: it is a client-side option added in modelcontextprotocol/typescript-sdk PR # 1105 (SEP-1036, November 2025). When set, the SDK's elicitation/create handler wrapper fills default values from requestedSchema into fields the user left blank before returning the result. The SDK does not set it by itself; the constructor copies whatever capabilities object the caller passes straight into initialize, and that is how it reaches the wire.

Consequences for this probe:

  1. Any client built on @modelcontextprotocol/sdk that opts in with capabilities: { elicitation: { form: { applyDefaults: true } } } sends exactly this. OpenCode is one such client today. Every new TypeScript-SDK host that copies the same snippet, or wants blank-field defaulting, will send it too.
  2. Any non-SDK client can send it as well, since form is open by spec. There is already a known case of the reverse problem: MCP Java SDK 0.18 deserialized form strictly and rejected initialize over this key (opencode issue # 47502, tolgee PR # 3918). The right treatment of the field is "unknown key, ignore", not "identity".
  3. Combined with name == "cli" and major >= 2, the fingerprint is three unrelated facts that coincide in OpenCode 2.0.16. None of them is owned by OpenCode. The test even asserts a hypothetical cli 3.1.0 as OpenCode.

The underlying defect is OpenCode reporting clientInfo.name as cli (process.env.OPENCODE_CLIENT ?? "cli", never set by serve). That is an upstream bug and belongs in an OpenCode issue. On our side the mcp.text_only knob already covers it, so the README should document how to set it for OpenCode 2 rather than the server guessing from a capability flag.

One thing to keep from this PR regardless: the server must stay lenient on unknown keys under capabilities.elicitation.form. Reading params as a serde_json::Value already does that, so no change is needed there.

…dai44423#306)

The fingerprint (name "cli" plus major >= 2 plus
elicitation.form.applyDefaults) is a coincidence of three facts none of
which OpenCode owns: the capability is a TypeScript-SDK opt-in any
SDK-based client can send and future clients will, so the match would
misfire on later clients while claiming a certainty the handshake does
not carry. compat.rs documents its layers; the manual override is layer
3, designed for exactly this case ("fixed today without waiting for a
release"), and the list stays name-based with its exit plan.

Kept from the original: the mcp.text_only description fix and the README
coverage of OpenCode 2. An upstream report for the actual defect
(clientInfo.name defaults to "cli") is drafted.

compat.rs is back at master; no behavioral change ships in it.
@dondai44423 dondai44423 changed the title fix(mcp): OpenCode 2 gets the text-only fold, recognised by its handshake docs(mcp): document the text-only knob for OpenCode 2; fix the mcp.text_only description Sep 25, 2026
@dondai44423

Copy link
Copy Markdown
Owner

Reworked on this branch per the review: the fingerprint and its test are gone; the description fix and the README coverage are kept, with the README now documenting the knob explicitly (toml form, new-style env name, legacy-name note).

@mnaza the write-up and the description fix ship as-is, thanks. @Mart-Bogdan your read decided it: the capability is an SDK opt-in, not an OpenCode signature, and the compat layer already has the designed path for this case. The actual defect is upstream.

compat.rs is back at master, byte-identical; the diff is README + CHANGELOG + one description line.

@dondai44423
dondai44423 dismissed Mart-Bogdan’s stale review September 25, 2026 17:58

Reworked on the branch per this review: the handshake fingerprint is dropped, the mcp.text_only description fix and the README coverage stay, and the upstream defect is noted as an OpenCode report. compat.rs is back at master.

@dondai44423
dondai44423 merged commit 627dc7d into dondai44423:master Sep 25, 2026
8 checks passed
@mnaza
mnaza deleted the fix-opencode2-client-detection branch September 25, 2026 18:27
@mnaza

mnaza commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator Author

@Mart-Bogdan agreed, and thanks for the SDK background: I took applyDefaults for an OpenCode signature from the report's capture and did not check the SDK, where it is an opt-in any TypeScript-SDK host can copy. Three coinciding facts are not an identity, and with mcp.text_only already there the knob plus a README section is the right shape. The defect belongs upstream with OpenCode.

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.

[bug] OpenCode 2.0 reports clientInfo.name "cli", misses text-only detection; web_fetch reaches the model as metadata only

3 participants