Repository navigation
feat(mirrorhost): opencode sessions can use the preview tools - #591
Merged
Merged
Conversation
opencode resolves a remote MCP server's headers once, in its one shared background service, so pane headers would attribute every opencode session to the service's pane. The one per-session signal it sends is params._meta["ai.opencode/sessionID"] on every tools/call. /preview-mcp now recognises an opencode caller by a static X-Muster-Client: opencode header. Such a request skips the tmux-server check (the service runs in no pane; secret, Origin and POST checks are unchanged), initialize/tools/list answer as for any caller, and a tools/call is bound to the ONE window whose active opencode pane shows that session, or its nearest ancestor (a subagent's child session), through a binding muster sends on (launcher record or verified title binding). No id, an unknown id, an unverified title match, or two windows showing it is no_window. Pane headers on an opencode-marked request are ignored: they could only be the service's own. The id selects among this host's own mirrored windows behind the same secret; a forged id can only reach another of the owner's windows on this host. /mcp (muster's tools) does not honour the header. Tests: red at the base (an opencode caller gets the outsider's empty tool list; with pane headers it is bound to the pane's window), green here; 15 mutants of the resolution, guard, ancestry walk and _meta read all killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MCPProxy.openCodeWindow matches only an opencode pane's session: the id it resolves is opencode's own _meta key, so the runtime check is deliberate and belongs in the ledger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Round 0 + round 1 on 3cea184: no operator premise, no 🔴 — ladder ends. The secret stays mandatory (Origin → secret → POST → marker), the marker only skips the tmux-server routing check and only on /preview-mcp (/mcp passes opencodeOK=false; mutant caught), lookups are fresh per call and fail closed. Recorded open (🟢, not fixed): no test for the cycle check in Ancestors (depth cap still bounds it); binding needs opencode to be the window's active pane; a launcher-bound pane keeps its original session id. 🟢4 resolved by evidence outside this PR: opencode puts a server's initialize instructions into the system context (pinned opencode 6ec5682: mcp/instructions.ts → session/context.ts:139). |
ZacxDev
marked this pull request as ready for review
November 11, 2026 05:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Mirrored opencode sessions could not use the preview tools. opencode resolves a remote MCP server's headers once, in its one shared background service. Pane headers would therefore name the service's pane for every opencode session, and every preview would land in the wrong window. The only per-session signal opencode sends is
params._meta["ai.opencode/sessionID"]on everytools/call(packages/core/src/mcp/client.ts, read at the pinned opencode revision).What
/preview-mcponly./mcp(muster's tools) does not change.X-Muster-Client: opencodeis an opencode caller. It skips the tmux-server check, since the service runs in no pane. The secret,Originand POST checks still apply.initializeandtools/listneed no session. They return the instructions and the three tools as for any caller.tools/callis bound to the one window whose active opencode pane shows that session, or the session's nearest ancestor (a subagent's child session calls with its own id). The ancestors come from opencode's session list (OpenCodeSource.Ancestors). The binding must be one muster also sends on: the launcher's record, or a verified title binding.no_window: no session id, an id no window shows, an id shown only through an unverified title match, and an id shown in two windows._metasession id, the pane still wins.Security: the session id selects a window. It does not authenticate. It is believed only among this host's mirrored windows, behind the same per-host secret, so a forged id can at most bind a preview to another of the same owner's windows on this host. A Claude Code caller can already do that by naming another pane. An unknown id binds nothing.
Docs:
docs/attention.md(new "opencode sessions" subsection with the config entry),docs/previews.mdsetup step 6, and the opencode non-goal indocs/design/previews.md(now an as-built note).opencode MCP entry (opencode config, under
mcp)Tests
internal/mirrorhost/mcpproxy_opencode_test.go: session → window (launcher-bound, verified title, child and grandchild); fail-closed cases; the marker vs pane-header precedence; the endpoint end to end (instructions, tools,no_windowtool errors, 401 without the secret, nothing forwarded); the marker is not honoured on/mcp, and an unmarked outsider is still an outsider;Ancestorsagainst the opencode fake server.internal/previewhost/mcp_test.go: the_metaid reachesCallerfor every tool. A missing id, a non-string id or another key reads as none."@1", the service's), not its session's./mcp, the marker never skipping), the ancestry walk, the_metaread and trimming. Source checksums were verified after the battery.internal/mirror/runtime_ledger_test.go:MCPProxy.openCodeWindowis added to the runtime ledger on purpose. It namesRuntimeOpenCodebecause the id it resolves is opencode's own_metakey. The first CI run went red on this ledger, and the second commit fixes it.go test -raceon./internal/previewhost ./internal/mirrorhost ./cmd/muster-mirror: ok. Leak scan: clean.Not verified: a live opencode session calling the tools. That needs the entry above on a host with previews on.
🤖 Generated with Claude Code