Skip to content

feat: add remote mcp access to runtimes through alera cloud - #924

Merged
leynier merged 15 commits into
mainfrom
feat/remote-runtime-mcp
Oct 8, 2026
Merged

leynier merged 15 commits into
mainfrom
feat/remote-runtime-mcp

Conversation

@leynier

@leynier leynier commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Summary

Runtimes (desktop sidecar or alera runtime start) can opt in to MCP Control and become reachable from any MCP client (Claude, ChatGPT, Cursor, Claude Code) through one OAuth-protected endpoint, https://api.alera.build/v1/mcp. Several runtimes can be granted at once; every tool call picks one by name or id (list_runtimes lists them). Full design and contract: docs/remote-mcp.md.

  • Cloud (authorization server + gateway): RFC 8414/9728 metadata, Dynamic Client Registration, Client ID Metadata Documents (HTTPS only, SSRF guard), consent page that grants specific runtimes or all runtimes plus mcp:read/mcp:execute, PKCE, single-use codes, rotating refresh tokens, revocation, device authorization for headless runtimes, PUT /v1/runtime/name and /v1/runtime/capabilities, and a gateway that resolves the runtime, writes a metadata-only audit row, and signs a 120 s per-call grant. Migration 0022_remote_mcp.sql.
  • Edge: stateless Streamable HTTP MCP endpoint (protocol versions 2025-03-26 through 2025-11-25), local JWT verification, dedicated sign-in and MCP rate limiters, and Durable Object routing of calls over the runtime's existing relay socket using reserved ~mcp frames. Phones cannot use ~ client ids, and runtimes with Remote Access off are hidden from phones.
  • Runtime/CLI: MCP Control level (off/read/full) in runtime metadata; the relay link runs when Remote Access or MCP Control is on; call grants are verified against JWKS with replay protection before a tool runs. 31 tools, each a typed alera <group> --json invocation (values passed as --name=value, long text on stdin). New commands: alera account status|login [--device]|logout, alera runtime rename, alera mcp status|enable [--read-only]|disable|apps|revoke|tools|serve.
  • Desktop: Settings > MCP Access (access level, runtime name, endpoint, link status, connected apps with revoke).
  • Infra/docs: optional web OAuth client variables and mcp_enabled in OpenTofu, OAUTH_LIMITER/BROWSER_LIMITER/MCP_LIMITER bindings, updated cloud rules, relay and backend docs, and the alera-cli skill.

Validation

  • cloud: fmt, clippy -D warnings, cargo test --workspace (43 unit tests) and the Postgres contracts with --include-ignored (OAuth flow, device flow, gateway, revocation) against a throwaway Postgres 17.
  • edge: bun run check, bun test 90/90.
  • rust: clippy -D warnings; full cargo test -p alera-cli 1915/1916. The one failure, output_that_keeps_streaming_is_still_coalesced, is a PTY timing test unrelated to this change and passed 3/3 when rerun.
  • Desktop: flutter analyze clean, 23 new unit and widget tests, max-lines ratchet ok.
  • Local end-to-end acceptance against a real stack: Postgres, cloud, edge on wrangler dev --local, an isolated runtime, and a stub GitHub provider. Covered:
    • device sign-in, rename, enable;
    • an MCP client doing DCR, authorize, consent, token, refresh, initialize, tools/list and list_runtimes;
    • tool calls by implicit runtime and by name: creating a worktree workspace, writing to and reading from a terminal;
    • read-only runtime, read-scope token, disable and re-enable, revoking a connected app (immediate 401), audit outcomes, and alera mcp serve over stdio.
  • The acceptance run caught and fixed two bugs unit tests missed: the edge called fetch with the wrong this (workerd "Illegal invocation"), and the runtime's MCP level went stale in the cloud after it was turned off.
  • A security review pass led to these fixes:
    • sign-in rate limits that no longer break device polling;
    • authorization errors render a page instead of redirecting;
    • private-use redirect schemes are restricted;
    • device sign-in is refused for runtimes that are still signed in, and the confirm page warns before reconnecting an existing runtime;
    • the consent page shows who published the client;
    • unreadable calls get an immediate answer.

Risks and follow-ups

  • Privacy boundary: with MCP Control on, tool arguments and results cross the edge in plaintext, not stored. This is opt-in per runtime and is documented. The edge stays inside the trust boundary: it serves the JWKS and forwards arguments, which the call grant does not cover.
  • Production setup: web OAuth clients for https://api.alera.build/oauth/callback (GitHub and Google) must be created and wired through the new variables; until then the native clients are reused and may reject the web callback. The edge needs the new rate-limit namespaces.
  • Not yet tested by hand: hosted clients (claude.ai, ChatGPT) against production, and the desktop MCP Access pane against a real runtime.

Runtimes started from the desktop or the CLI can opt in to MCP Control and
become reachable from any MCP client through one OAuth-protected endpoint,
https://api.alera.build/v1/mcp. Each call names its runtime by name or id.

- cloud: OAuth 2.1 authorization server (DCR, client metadata documents,
  consent, PKCE, refresh rotation, revocation), device sign-in for headless
  runtimes, runtime naming and capability reports, gateway that resolves the
  runtime, records a metadata-only audit row, and signs a per-call grant
- edge: stateless Streamable HTTP MCP endpoint, sign-in rate limiters, and
  Durable Object routing of calls over the runtime's relay link (~mcp frames)
- runtime: MCP Control setting (off, read, full), call grant verification,
  31-tool catalog backed by typed `alera --json` invocations, `alera mcp`,
  `alera account`, and `alera runtime rename`, plus `alera mcp serve` (stdio)
- desktop: MCP Access settings with access level, runtime name, endpoint,
  link status, and connected apps
- infra and docs: optional web OAuth clients, MCP switch, docs/remote-mcp.md

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Remote MCP Control is opt-in (off / read / full, default off) and the trust boundaries hold.

OAuth and device sign-in. Public clients only, PKCE S256, exact redirect match (https, loopback http, reverse-domain and known-app schemes; javascript/data/file refused). Authorization errors render a page instead of redirecting once the client is known. Codes are single-use and a replay revokes the grant. Device flow is runtime-only; a runtime with a live refresh family cannot start another sign-in. User codes are 8 chars from a 32-char alphabet, bounded by the edge browser limiter.

Call gateway. POST /v1/mcp/calls is not on the public edge route. The grant binds account, runtime, tool, access, and jti (120s, aud: alera-runtime-mcp). MCP access tokens use a distinct audience and are rejected by other cloud routes. Arguments are not in the grant; the edge is inside the trust boundary and the runtime re-verifies before spawn. Audit rows are metadata only.

Edge endpoint. Stateless Streamable HTTP. Tool execution rechecks the live grant, so a revoked 15-minute JWT still fails the call. list_runtimes is edge-only; the runtime argument is stripped before the runtime sees the payload. ~mcp frames are not forwarded to phones. Frame cap is 1 MiB.

Runtime. Each tool is one alera --json invocation with --name=value (no shell). Context env vars are stripped. jti is recorded until exp. Local settings.mcp.access is checked again before spawn. mcp.* and account. stay off the mobile allowlist and the hub reverse channel.

Desktop. Settings > MCP Access is local-client-only. Unknown wire access falls back to off. mcpSettingsChanged is in runtimeHostEventNames.

Production follow-ups are documented, not defects: create the ALERA_WEB_* OAuth clients, and bind OAUTH_LIMITER / BROWSER_LIMITER / MCP_LIMITER on the edge.

Pullfrog  | View workflow run | Using grok-4.7 | 𝕏

leynier added 12 commits October 7, 2026 21:49
Keep the confirmation token while a device sign-in is pending, refuse expired approved codes, cancel queued and running MCP calls when the relay link ends, accept long metadata-document client ids in call grants, and cap runtime names at the 160-byte sign-in label limit.
send_message and delegate_task now take the sender or coordinator terminal the CLI requires, a cancelled call never starts its command even while queued, calls stop when the socket they arrived on closes, and request traces record only the path so OAuth codes and state never reach the logs.
…filters

Call grants are rejected if they expire while signing keys load, queued stdio calls never start once cancelled, an idle runtime session no longer blocks device sign-in, and list_tasks accepts stalled and cancelled.
…loud

The stdio server cancels running and queued calls when the client closes stdin or stdout, a wait that ends with nothing new is no longer reported as a tool error, and renaming a runtime needs a signed-in account so the cloud reserves the name first.
… on 429

cancel_task runs as an audited administrative cancellation tagged [mcp], a device sign-in cancelled while its approving poll is in flight revokes the session instead of saving it, and edge rate-limit responses keep the route's CORS headers.
… polls

The edge forwards callback and device query strings to Cloud Run as form bodies and the Worker runs without invocation logs, so codes and state never reach request logs. A cancelled device sign-in now lets its in-flight poll finish and revokes any session it returns.
…of logs

Device code redemption takes a per-runtime advisory lock and rechecks for an active session, so a second approved code cannot sign in an already active runtime. The edge forwards /oauth/authorize queries as form bodies like callbacks and device pages.
…y its own outcome

Device redemption creates its session inside the transaction that holds the per-runtime lock, so concurrent redemptions cannot exhaust the pool. The runtime records how the latest sign-in attempt ended and the CLI reports that instead of treating an older session as success.
…directing declined logins

The replay record of MCP call grants survives relay restarts, a provider callback without a code renders a page instead of redirecting to the client, and a cancelled sign-in records its outcome at once so the CLI never reports it as a success.
…e browser runs

Device redemption builds the signed response inside its transaction, so a signing failure leaves no orphaned session that would block the next device sign-in. alera account login no longer waits for the browser opener to exit before polling.
A signing failure during refresh no longer spends the client's refresh token, and database failures surface as server_error instead of invalid_grant. Native rotation keeps its behavior through a wrapper.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues. One edge case in cancelled device sign-in is noted inline.

Reviewed changes

Re-reviewed the twelve commits since a340aa4. They harden OAuth logging, device redemption, refresh rotation, and MCP call lifetime.

  • Moved authorization, callback, and device query strings into POST bodies so Cloud Run request logs record only the path.
  • Stopped redirecting a declined provider login, because that callback is reachable without authentication.
  • Redeemed one device code per runtime under an advisory lock, and signed the session before that transaction commits.
  • Rotated MCP refresh tokens and signed the replacement access token in the same transaction, so a signing failure does not spend the token.
  • Kept consumed call grants for the process lifetime, and rechecked expiry after signature verification.
  • Stopped in-flight MCP calls when the relay socket or stdio client disconnects, and kept queued calls from starting after cancel.
  • Required a coordinator and a sender handle on orchestration tools, and recorded MCP task cancellation as an audited force cancel.
  • Listed every project's workspaces when projectId is omitted, and reserved runtime names in the cloud with a 160-byte cap.
  • Polled device sign-in while the browser opens, and judged the attempt by its own outcome rather than an existing session.
  • Kept CORS headers on edge 429 responses.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using grok-4.7 | 𝕏

Comment thread rust/alera-cli/src/terminal_host/server/mcp_requests.rs Outdated
…q, and never drop device sessions

The edge catalog check compares parsed JSON with sorted keys, since workspace builds enable serde_json preserve_order. wrangler.jsonc drops its comment so the deploy script's jq can read it. A cancelled device sign-in whose session cannot be revoked keeps it and says so, and a session that cannot be saved is revoked.
Autovacuum can hold the lock CREATE INDEX CONCURRENTLY waits for after the million-row insert, so the 100ms timeout fired before the invalid index existed on slow runners.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Re-reviewed the two commits since 583e6e2. They close the cancelled device sign-in gap and fix two checks that were failing around that change.

  • Kept device sessions that could not be revoked. A cancelled approval now revokes the cloud session, and if that revoke fails the session is saved and the error tells the person to run alera account logout. A session whose local save fails is revoked instead of dropped.
  • Made the tool catalog check order-free. The edge copy is compared as parsed JSON, and keys are sorted before a rewrite, so a workspace build with serde_json's preserve_order no longer fails the string compare.
  • Let deploy scripts parse the Worker config. The comment in edge/wrangler.jsonc is gone so jq can read it. Invocation logs stay off, and the docs now include /oauth/authorize among the queries forwarded as form bodies.
  • Stopped autovacuum from racing the interrupted-index contract. auth_transactions has autovacuum disabled around the million-row insert, so the 100ms timeout still leaves an invalid index for the repair check.

Pullfrog  | View workflow run | Using grok-4.7 | 𝕏

@leynier
leynier merged commit 4fb76ed into main Oct 8, 2026
27 checks passed
@leynier
leynier deleted the feat/remote-runtime-mcp branch October 8, 2026 15:31
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