Skip to content

feat(studio): provide a trusted host bridge for the local MCP server - #96

Merged
7shep merged 2 commits into
mainfrom
agent/issue-59-host-bridge
Jul 31, 2026
Merged

feat(studio): provide a trusted host bridge for the local MCP server#96
7shep merged 2 commits into
mainfrom
agent/issue-59-host-bridge

Conversation

@7shep

@7shep 7shep commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Studio had a typed client and an MCP transport interface but nothing that actually implemented it against a real session — browsers cannot open a stdio pipe. This adds the trusted host bridge.

Studio (browser, untrusted)
   │  HostArtDirectorTransport — 8 named operations over fetch
   ▼
Trusted local runtime (loopback HTTP)
   │  POST /api/v1/art-director/<operation>
   │  ArtDirectorBridge — allowlist, validation, session ownership, timeout, retry
   ▼
Art Director MCP server (stdio child process)

docs/STUDIO_HOST_BRIDGE.md documents the architecture and trust boundary, and is the thing to read first. docs/STUDIO.md now points at it from the transport section.

Trust boundary

The bridge lives inside the runtime host that already owns generation, materialization, builds, and previews, so it inherits loopback binding, one-time bootstrap, the HttpOnly session cookie, Host/Origin checks, the body quota, and structured RuntimeError responses. On top of that:

  • Eight operations, fixed. The URL path segment is looked up in an allowlist; the MCP tool name always comes from that table, never from the request. An unknown operation is rejected, and own-property lookup means __proto__ is not an operation.
  • Fields are rebuilt, not forwarded. Each operation declares its shape. Everything else in the body is dropped — a test asserts that { command, path } reaches the MCP tool as nothing at all.
  • The host owns the session. The complete serialized session is held host-side and refreshed on every mutation. The browser's copy is compared, never trusted: a request echoing a different session is STALE_ARTIFACT rather than a silent replay over newer decisions.
  • Calls are serialized. One mutable session means overlapping mutations would lose one.
  • No credentials are read or forwarded, and no operation accepts a path or a command.

This is not a general MCP proxy, and adding an operation is a deliberate reviewable change to the allowlist.

Failure handling

Reconnect (one retry after closing a dead stdio session; MCP operations are idempotent by request id), timeout, cancellation, and structured errors. A session-engine rejection like INVALID_SESSION is reported unchanged and never replayed — it is an answer, not an outage. The bridge does not assume the transport honours an abort signal; it stops waiting either way and discards the session so nothing arrives late on a connection a later request would reuse.

The fixture client is preserved

pnpm dev is unchanged. Studio only switches to the real session when the runtime advertises a bridge at GET /api/v1/art-director/operations; otherwise it keeps createLocalArtDirectorClient(), so the four-stage workflow still needs no runtime, no stdio session, and no credentials. The component tests keep injecting their own fake.

Tests

packages/local-runtime/test/art-director-bridge.test.ts — 11 tests, no subprocess, via an injected ArtDirectorMcpSession:

happy path across the workflow · the allowlist · rejected operation / tool name / prototype key / non-object body · dropped extra fields · operation before any session · stale session · transport failure with successful reconnect · persistent transport failure · domain rejection that must not be replayed · response missing the session · timeout · cancellation · serialized concurrent calls.

Validation

  • node --test test/art-director-bridge.test.ts test/http-server.test.ts — 12 passing
  • pnpm --filter @universal/studio test — 3 passing
  • pnpm typecheck, pnpm lint, pnpm format:check
  • pnpm --filter @universal/studio build

Not done: no end-to-end run against a live stdio MCP server. Everything here is exercised through the injected session port, and step 2 of the setup guide is the wiring a runtime host would add. Worth one manual pass before merge.

Closes #59.

🤖 Generated with Claude Code

Studio had a typed MCP transport interface but nothing implementing it
against a real session, because a browser cannot open a stdio pipe.

Add a narrow allowlisted bridge inside the trusted runtime that owns the
serialized session, plus the Studio-side transport. The deterministic
fixture client stays the default.

Closes #59.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
universal Ready Ready Preview Jul 31, 2026 6:52pm

@7shep
7shep merged commit 29b2a98 into main Jul 31, 2026
7 checks passed
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.

feat(studio): provide a trusted host bridge for the local MCP server

1 participant