Skip to content

feat(agent-adapter,engine,daemon): simulator MCP tools for every MCP-capable agent (CODE-395)#254

Open
AprilNEA wants to merge 1 commit into
xuan/code-394from
xuan/code-395
Open

feat(agent-adapter,engine,daemon): simulator MCP tools for every MCP-capable agent (CODE-395)#254
AprilNEA wants to merge 1 commit into
xuan/code-394from
xuan/code-395

Conversation

@AprilNEA

Copy link
Copy Markdown
Member

Summary

Stacked on #251. Agents get the simulator through MCP — the CODE-93 passthrough plus the daemon's built-in endpoint, in one atomic change (the pieces are cross-package-interdependent):

  • MCP passthrough (CODE-93 core): StartOptions.mcpServers (stdio + http) now reaches claude-code (options.mcpServers record), codex (thread/start config dotted keys mcp_servers.<name>.*; an http entry arms experimental_use_rmcp_client; http headers have no config key → loud reject), and opencode (createOpencode config.mcp, http→remote / stdio→local). pi rejects a non-empty mcpServers loudly — and the engine never injects for it.
  • Engine injection: SessionStartOptionsResolver appends the per-session endpoint for MCP_CAPABLE_AGENT_KINDS on every start/resume path; EngineDeps now takes the daemon-constructed SimulatorService (+ SimulatorMcpProvider) so the MCP endpoint and the engine share one claims registry; session stop releases claims and tokens.
  • Daemon endpoint: loopback streamable-HTTP MCP server (@modelcontextprotocol/sdk), one session-bound token per session, stateless per-request servers. Tools: sim_list_devices/boot/shutdown/install/launch/terminate/open_url/screenshot — screenshot returns MCP image content, so the capture lands in the agent's context. Ownership/caps enforced via the shared service; simulator.activity broadcasts (new wire variant, still protocol 44) drive the panel's “agent is driving this device” badge, and MCP-path boot/shutdown push simulator.devices.changed like the wire handler does.

Verification

  • Unit: adapter mapping (incl. codex header rejection), resolver injection gating (pi excluded, absent capability), endpoint integration over a real MCP client (tool list, image content, cross-session conflict, token revocation). pnpm check:ci + pnpm test (1785) green.
  • Live acceptance against a dev daemon: real claude-code (2.1.216) and codex (0.144.1) sessions each autonomously ran sim_list_devices → sim_boot (iPhone 17) → sim_screenshot → sim_shutdown through the injected endpoint — activity and devices.changed broadcasts observed client-side. The codex run also live-verifies the mcp_servers.* dotted-key + rmcp-client bet. opencode passthrough is SDK-typed and unit-tested; live run pending an opencode install.

@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

CODE-395

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc4d881a07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +39 to +44
z.object({
kind: z.literal('simulator.activity'),
sessionId: SessionIdSchema,
udid: z.string().min(1).optional(),
tool: z.string(),
phase: z.enum(['started', 'settled']),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bump the wire protocol for simulator activity

Adding simulator.activity while retaining protocol version 44 lets an older v44 client complete the handshake but silently reject these broadcasts, so the activity badge never updates and the incompatible peer is not detected. Increment WIRE_PROTOCOL_VERSION whenever introducing this wire variant.

AGENTS.md reference: AGENTS.md:L9-L9

Useful? React with 👍 / 👎.

/** Append the session's simulator MCP endpoint for agents whose SDK can consume it. */
private withSimulatorMcp(options: StartOptions, sessionId: SessionId): StartOptions {
if (!this.simulatorMcp || !MCP_CAPABLE_AGENT_KINDS.has(options.kind)) return options;
const endpoint = this.simulatorMcp.endpointFor(sessionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Revoke MCP tokens when session startup fails

When translation resolution or adapter startup fails after this call, the session never reaches normal teardown: SessionOrchestrator.discardFailedStart removes the session without invoking onStopped, and a translation failure occurs before startLive entirely. Consequently the endpoint's token maps grow on every failed attempt, and a partially started adapter that received the URL retains a valid simulator endpoint for a rejected session; release the token on every resolver/start failure as well as normal stops.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR gives MCP-capable agents access to session-scoped simulator tools. The main changes are:

  • A loopback simulator MCP endpoint with ownership enforcement and image responses.
  • Per-session endpoint injection across session start and resume paths.
  • MCP configuration mapping for Claude Code, Codex, and OpenCode.
  • Simulator activity broadcasts and client subscriptions.

Confidence Score: 4/5

The change requires attention before merging.

Normal session ownership and token release are implemented consistently, but pre-live failures do not consistently roll back minted tokens. Duplicate endpoint names can silently replace caller-provided MCP configuration, and large or aborted POST bodies can consume daemon memory and connections.

packages/host/engine/src/session/start-options-resolver.ts and apps/daemon/src/sim/mcp-endpoint.ts

T-Rex T-Rex Logs

What T-Rex did

  • Ran a focused engine lifecycle regression test that rejects adapter startup and exercises the retained MCP credential, and the reproduction confirmed the MCP URL remained usable after the failure.
  • Executed a resolver and Claude adapter collision test, observed two entries with the same name, and verified that the final mapping pointed to the injected simulator endpoint instead of the caller endpoint.
  • Exercised the real SimulatorMcpEndpoint with a large POST and confirmed unbounded-body behavior, including an 8,388,786-byte read followed by a 400 MCP response and a second large request with an aborted transfer.
  • Compared pre-change and post-change traces, observing that the endpoint source did not exist before HEAD^ and that the post-change run produced 1 passed file and 3 passed tests, with tool traces and a second-session error 'in use by another session'.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
apps/daemon/src/sim/mcp-endpoint.ts Adds the session-token HTTP endpoint and simulator tools, but request-body handling permits resource exhaustion.
packages/host/engine/src/session/start-options-resolver.ts Injects the simulator endpoint, but token creation lacks failure rollback and the fixed server name can replace caller configuration.
packages/host/engine/src/session/lifecycle-service.ts Propagates session IDs through all start and resume paths.
packages/host/engine/src/engine.ts Shares the simulator service and releases claims and endpoint tokens during normal session teardown.
packages/host/agent-adapter/src/native/claude-code.ts Maps HTTP and stdio MCP entries into Claude Code's keyed SDK configuration.
packages/host/agent-adapter/src/native/codex/adapter.ts Adds Codex MCP overrides while preserving writable-root configuration and rejecting unsupported headers.
packages/host/agent-adapter/src/native/opencode/adapter.ts Merges local and remote MCP entries with OpenCode provider configuration.

Sequence Diagram

sequenceDiagram
participant Client
participant Engine
participant Resolver
participant Endpoint
participant Agent
participant Simulator
Client->>Engine: Start session
Engine->>Resolver: Resolve options with session ID
Resolver->>Endpoint: Mint tokenized endpoint
Resolver-->>Engine: Options with MCP server
Engine->>Agent: Start with MCP configuration
Agent->>Endpoint: Tool request with token
Endpoint->>Simulator: Operation under session ownership
Simulator-->>Endpoint: Result or ownership error
Endpoint-->>Agent: MCP response
Engine->>Endpoint: Release token on normal stop
Loading

Reviews (1): Last reviewed commit: "feat(agent-adapter,engine,daemon): simul..." | Re-trigger Greptile

/** Append the session's simulator MCP endpoint for agents whose SDK can consume it. */
private withSimulatorMcp(options: StartOptions, sessionId: SessionId): StartOptions {
if (!this.simulatorMcp || !MCP_CAPABLE_AGENT_KINDS.has(options.kind)) return options;
const endpoint = this.simulatorMcp.endpointFor(sessionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Failed Starts Retain MCP Tokens

endpointFor stores a bearer token while options are being resolved, before translator startup, workspace setup, and adapter startup can fail. Those failure paths do not all invoke the normal stop callback, so each failed start can leave a valid endpoint and two map entries until daemon shutdown.

Artifacts

Repro: focused engine lifecycle regression test that rejects adapter startup and exercises the retained MCP credential

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: verbose Vitest output showing the deliberate start failure and successful post-failure MCP tool listing

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

if (!this.simulatorMcp || !MCP_CAPABLE_AGENT_KINDS.has(options.kind)) return options;
const endpoint = this.simulatorMcp.endpointFor(sessionId);
if (!endpoint) return options;
return { ...options, mcpServers: [...(options.mcpServers ?? []), endpoint] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Injected Name Overwrites User Server

When the caller already supplies an MCP server named linkcode-sim, appending the built-in endpoint creates a duplicate name. Each changed adapter converts this array into keyed configuration with last-entry-wins behavior, so the injected endpoint silently replaces the caller's server and its tools.

Artifacts

Repro: focused resolver and Claude adapter collision test

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: focused Vitest configuration used to execute the test

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: verbose failing test output showing duplicate resolved entries and last-entry-wins keyed configuration

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +302 to +307
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
req.on('data', (chunk: Buffer) => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
req.on('error', reject);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Request Bodies Grow Without Bound

A local MCP client can stream an arbitrarily large POST, which retains every chunk and then allocates the combined buffer. A client that disconnects before end can also leave the promise pending because aborted and premature close are not handled, allowing faulty or compromised agent processes to exhaust daemon memory or connections.

T-Rex Ran code and verified through T-Rex

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.

1 participant