Repository navigation
feat(freshclaude): freshclaude and kilroy panes get the Freshell MCP tools, following the Agent MCP setting - #873
Merged
Merged
Conversation
…d agents Both build_terminal_base_env copies (freshell-ws terminal.rs and freshell-freshagent terminal_tabs.rs) now delegate to freshell_platform::freshell_context_env, which also serves the freshclaude/kilroy sidecar next. Pinned by the existing base_env tests and the terminal_tabs env tests; the js_number_string test moves with the function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he fixed MCP recipe The session host already selects the hosted Freshell MCP for kilroy lanes (kilroy consumes the Claude launch recipes), but the sidecar offered the fixed recipe and the managed exec check to hosted claude only, so a hosted kilroy pane had no Freshell MCP at all and spawned its CLI unchecked. Both rules now apply to every hosted Claude runtime (api-hosted-ops plan §2.4 open decision). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the pane context
mcp-servers.mjs (absorbing hosted-mcp.mjs) picks the session's one
Freshell MCP recipe: the hosted fixed command when a hosted lane selected
it, otherwise the command the server resolved (create.freshellMcp); a
hosted sidecar never gets the ordinary one, and no recipe ever carries an
env key (the SDK puts the MCP config on the CLI's argv). The create's
freshellContext ({tabId, paneId}) is merged into the SDK env, replacing
any inherited pane-scoped names. The simulator ledger now records the
Freshell context values (and a digest of the token) so tests can see what
the CLI environment carried.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreshAgentCreate gains an optional paneId (tabId already existed) and FreshAgentAttach optional tabId/paneId, in the zod schema, the frozen contract and the Rust structs. Absent fields stay absent on re-serialize and older frames still parse. The pane context is what the agent's Freshell MCP will act for (G19). Existing struct literals get the new fields as None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dpoint and its pane A claude lane can now be wired with the server's endpoint and a Freshell MCP recipe provider (set-once, like the identity sink). Every sidecar's environment carries FRESHELL=1, FRESHELL_URL and FRESHELL_TOKEN from that endpoint and none of another pane's ids; the provider is asked at each launch in boot_sidecar, the one spawn path, so the recipe reaches the create, attach-resume, handoff resume, undo/redo respawn and fork alike, and a setting change applies to the next launch. A hosted lane never gets the ordinary recipe. ClaudeLaunchSpec carries freshellMcp and freshellContext; the pane comes from the create's or attach's tabId and paneId, and an undo replacement or fork child inherits its session's. Each launch logs freshagent.claude.mcp_injection, and an unresolvable command logs freshagent.claude.mcp_unavailable (warn) and starts the pane without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive setting main.rs hands the claude lane this server's own endpoint (its port and auth token) and a recipe provider that reads settings.codingCli.mcpServer from the live settings tree at every sidecar launch and resolves the same Freshell MCP command the terminal lanes use, rendered for this host's CLI. ServerSettings::agent_mcp_server_enabled is the one gate the terminal lanes will read next (#446). A PATCH of the setting turns the recipe off and back on with no restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
freshAgent.create now carries the pane's paneId beside its tabId, and freshAgent.attach carries tabId and paneId, so a freshclaude/kilroy sidecar launched or resumed for the pane runs its Freshell MCP for that pane (G19). The two tests that pin an attach frame exactly now expect the pane fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…act on the pinned CLI The real sidecar over the pinned SDK 0.3.237 / CLI 2.1.237 and a local provider, launched the way the Rust server launches a freshclaude pane: the CLI's argv carries the MCP config but never the token, the stdio MCP child receives FRESHELL, FRESHELL_URL, the token and the pane context (and no terminal id), the server connects (its tool is listed) and a model call of its tool returns what it saw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The simulator now starts each stdio MCP server in options.mcpServers with the SDK env (not the simulator's own environment), reports it connected or failed, lists its tools in init once a turn has waited for the servers to settle, calls a listed tool over tools/call (heartbeats while it runs), and answers a call of an MCP tool no server lists with the CLI's "No such tool available" error before the message's trailing stream. A new conformance scenario (mcp, marker MCP:call=<tool>) and the G19 fact test pin this on the pinned CLI 2.1.237: the server gets the CLI's environment, its tool is listed and called, and an unknown MCP tool is an error result. The full contract and simulator files stay green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…result A text emission with echoToolResult repeats what the turn's last tool call returned (after an optional text prefix), so a scripted model can show what an MCP tool answered. Pinned by a simulator test that calls a probe stdio MCP server through a program's tool step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings toggle removes it Through the real Rust server, the production sidecar and the CLI simulator: the agent's list-tabs call through the Freshell MCP server the server resolved lists the open tabs (its pane context and recipe recorded without the token), for freshclaude and kilroy; with Settings > Advanced > Agent MCP server turned off, a new pane's agent has no Freshell MCP and the same call is "No such tool available". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r the owner record
Under parallel load a fork could read the transcript before the CLI
flushed the turn ("nothing to fork yet"), and the owner record can trail
the first result. The tests now wait for both, the way the fork suite
does; 48 parallel runs stay green.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for the tool result The CLI flushes the tool_result line about 100 ms after the turn's frames, so reading the transcript right after the result could miss it under load (1 of 16 parallel runs). 18 parallel runs now pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g the sidecar No sidecar process waits on the settings read and command resolution. Pinned by the MCP launch and launch parity integration tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e browser tab id The agent API's tab list holds the tabs the server knows (here the tab the test created through REST); the browser's boot tab id is not part of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pts the session Switching a terminal Claude pane to freshclaude/kilroy, or stop-and-reopen, posts the handoff with the pane's tabId/paneId. The claude lane dropped them, so the resumed CLI and its Freshell MCP had no FRESHELL_TAB_ID/PANE_ID and an untargeted MCP call fell back to the user's active pane. The handoff now threads the ids into the resume's launch spec, for both flavors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ropped silently The server logs each launch's recipe as resolved before the sidecar checks it, so a recipe the sidecar could not use (a non-string argument, an empty command) left the pane without the Freshell MCP and no log said so. The sidecar now logs a warn record, mcp.freshell_recipe_rejected, with the session id, and still starts the session without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idecar-kill helpers The MCP launch test and the G15 launch-parity test each carried copies of query_options, wait_for_query, kill_the_sidecar and converse. They now live on ClaudeRig. The shared converse waits for each turn to reach the transcript before the next, so the parity test's resumes and undo no longer race the CLI's transcript flush either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plits its own pane The own-tab check was dropped after a cloud run where the server's tab list lacked the browser's tab. The cause is timing, not the MCP wiring: the browser mirrors its layout to the server after a debounce that every layout change restarts, and the simulated turn ran within that window, so the server knew only the tab the test created through REST. The spec now waits until the server knows the browser's tabs, then checks that the agent's list-tabs reply includes its own tab. It also covers the default target: while the model thinks, the user switches to another tab, and the agent's untargeted split-pane must split its own pane in its own tab and leave the active tab alone. Ignoring the pane context in the MCP tool's default target makes this fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What users see
Freshclaude and Kilroy panes now get the Freshell MCP tools, the same tools a Claude terminal pane already has. The agent in the pane can list tabs, split its own pane, send keys and so on. Calls that name no target act on the agent's own pane, not on the pane the user happens to be looking at.
The tools follow Settings → Advanced → "Agent MCP server". The setting is read each time an agent starts, so changing it applies to the next pane you open or resume, with no server restart. Terminal panes do not follow the switch yet (#446, out of scope here).
What changed
ClaudeLaunchSpec(claude_launch.rs) gained two optional fields:freshellMcp(the command and arguments of the Freshell MCP server) andfreshellContext(the tab and pane the session runs for). Every way an agent starts goes through it: create, attach-resume, handoff resume, undo/redo and fork. An undo replacement or a fork child keeps its parent's pane.main.rsinstalls a provider that readssettings.codingCli.mcpServerat each sidecar start (new modulecrates/freshell-server/src/claude_freshell_mcp.rs). When the setting is on but the server command cannot be found, the pane starts without the tools and a warning is logged.FRESHELL=1,FRESHELL_URLandFRESHELL_TOKENin its environment, built from the server's own port and token. The MCP recipe holds only a command and arguments, never anenvkey, so the token never reaches the CLI's argv. Pane ids inherited from elsewhere are removed; only the pane's own ids are set.mcp-servers.mjs(it replaceshosted-mcp.mjs) gives a hosted session the session host's fixed recipe and an ordinary session the recipe Rust resolved. A session never gets both. Hosted Kilroy now gets the hosted recipe too; before this, the sidecar refused it.freshAgent.createandfreshAgent.attachcarrytabId/paneId(protocol and client).freshagent.claude.mcp_injection(info) is logged at each launch, andfreshagent.claude.mcp_unavailable(warn) when the command cannot be resolved. In the sidecar,mcp.freshell_recipe_rejected(warn) is logged when a recipe arrives malformed and is left out.mcp). It hands them only the SDK's environment, and a program's text step can repeat the last tool result. The Rust claude rig gained shared helpers for readingquery()options, finishing turns on disk, and killing a sidecar.Tests (all on the final commit)
pnpm run test:vitest run test/unit/claude-sidecar/ test/unit/shared/ws-protocol.fresh-agent-pane-context.test.ts test/unit/client/components/fresh-agent/FreshAgentView.test.tsx test/integration/providers/claude-cli-sim.test.ts test/integration/providers/claude-freshell-mcp.test.ts test/integration/providers/claude-cli-contract.test.ts test/unit/mcp/freshell-tool.test.ts --config config/vitest/vitest.config.ts: 26 files, 931 passed. This includes the pinned-CLI contract test, which reads/proc/<pid>/cmdlineto confirm the token is not in the CLI's argv.freshell-ws(15 files): 100 passed. This includesfreshagent_claude_mcp_launch(8: create for both flavours, the setting changing between launches, the hosted lane, attach-resume, handoff resume for freshclaude and for kilroy, undo, fork). Each launch-test binary also ran 4 times in parallel: all green.cargo test -p freshell-freshagent --lib claude: 466 passed.--lib handoff: 103 passed.cargo test -p freshell-server --bin freshell-server claude_freshell_mcp: 2 passed.GCLOUD_ROBOT_REQUIRE=1 pnpm run test:e2e <specs> --project=chromium): 33 passed, 0 flaky, 0 failed, on the final commitab3cd3ba4.freshclaude-freshell-mcp-rust.spec.tscovers three cases. First, for freshclaude and for kilroy, the agent lists tabs through the real MCP server, and its own tab is in the list. Second, while the user is on another tab, an untargetedsplit-panesplits the agent's own pane and leaves the active tab alone. Third, with the setting turned off, the same call answers "No such tool available".fresh-agent-error-turn-attention,fresh-agent-rollback-rust,freshclaude-live-transcript-rustandfreshclaude-fork-rust.cargo fmt --check, clippy on the touched crates, andpnpm run typecheckare clean.Each fix was written test-first, and each test was seen failing before its fix. For the e2e default-target check, I also made the MCP tool ignore the pane context on purpose and confirmed the spec fails ("the agent's own tab gains the new pane": expected 2, received 1).
Follow-ups
test/unit/port/ws-contract-freeze.test.tshas 2 failures on main as well: the frozen server-message schema is missingmodelEffortLevels. It is not touched here.freshell-platformprovider_environmenttests count anode_modulesdirectory found above the test's temp directory. A stray/tmp/node_modules/wson the dev host made them fail in the pre-push gate. Not from this branch: the push ran withTMPDIRset to a clean directory, and they pass there. The tests should not depend on what sits above their temp directory.managed_provider_sdk_tests.rs(featuremanaged-provider-qualification, needs an owned sandbox) was updated to importmcp-servers.mjsbut was not run.🤖 Generated with Claude Code