# MCP proxy

> How Executor acts as a single MCP endpoint in front of OpenAPI, GraphQL, and upstream MCP integrations with shared auth and per-tool policies.

- Repository: RhysSullivan/executor
- GitHub: https://github.com/RhysSullivan/executor
- Human docs: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052
- Complete Markdown: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052/llms-full.txt

## Source Files

- `apps/docs/mcp-proxy.mdx`
- `packages/hosts/mcp/src/tool-server.ts`
- `packages/hosts/mcp/src/index.ts`
- `apps/cli/src/main.ts`
- `packages/core/api/src/server/mcp-build.ts`
- `apps/docs/concepts/integrations.mdx`

---

---
title: "MCP proxy"
description: "How Executor acts as a single MCP endpoint in front of OpenAPI, GraphQL, and upstream MCP integrations with shared auth and per-tool policies."
---

Executor exposes one MCP endpoint (`execute` and `resume`) that fronts every configured integration. Agents connect once; upstream OpenAPI APIs, GraphQL endpoints, and MCP servers are reached through the execution engine inside a QuickJS sandbox, with connection credentials and per-tool policies applied on each call.

## Architecture

```mermaid
flowchart TB
  subgraph clients["MCP clients"]
    A[Cursor / Claude Code / OpenCode / SDK]
  end

  subgraph executor["Executor MCP host"]
    E["/mcp or executor mcp"]
    TS["createExecutorMcpServer"]
    ENG["ExecutionEngine"]
    POL["Policy resolution"]
    CONN["Connections + credentials"]
  end

  subgraph upstream["Integrations"]
    M[MCP servers]
    O[OpenAPI APIs]
    G[GraphQL endpoints]
  end

  A -->|MCP stdio or streamable HTTP| E
  E --> TS
  TS -->|execute / resume| ENG
  ENG --> POL
  ENG --> CONN
  ENG -->|HTTP or MCP wire| M
  ENG -->|HTTP| O
  ENG -->|HTTP| G
```

The MCP surface does not mirror every upstream tool as a separate MCP tool. Clients call `execute` with TypeScript that invokes tools through the `tools.*` namespace (for example `tools.github.org.main.getRepo(...)`). The `execute` tool description is built dynamically from saved [connections](/connections) and [integrations](/integrations).

| Layer | Package / path | Role |
| --- | --- | --- |
| Serving envelope | `@executor-js/host-mcp` (`McpServingRoutes`) | Auth, CORS, session dispatch on `/mcp` |
| Tool factory | `@executor-js/host-mcp/tool-server` (`createExecutorMcpServer`) | Registers `execute` and `resume`, elicitation bridge |
| Session store | `McpSessionStore` seam | Per-session transport, ownership, browser approval |
| Local runtime | `apps/local/src/mcp.ts` | In-process streamable HTTP + stdio |
| Multi-user hosts | `packages/core/api/src/server/mcp-build.ts` | Per-principal engine via `makeMcpBuildServer` |

## MCP tools exposed to clients

Executor registers two MCP tools (three when elicitation mode is `native`):

| Tool | Input | Behavior |
| --- | --- | --- |
| `execute` | `code` (non-empty string) | Runs TypeScript in QuickJS with `tools.*` access to all connections |
| `resume` | `executionId`, `action`, `content?` | Resumes a paused execution (`model` mode only) |
| `resume` | `executionId` | Waits for browser approval (`browser` mode only) |

In `native` mode, elicitation is handled through MCP SDK `elicitInput` during `execute`; the separate `resume` tool is not registered.

The dynamic `execute` description lists connection prefixes, search/describe workflow, and sandbox rules. It is assembled from `executor.connections.list()` and `executor.integrations.list()` at session creation time.

## How integrations are proxied

An [integration](/integrations) defines the tool catalog (OpenAPI spec, GraphQL endpoint, or upstream MCP server). A [connection](/connections) binds owner-scoped credentials to that integration. Callable addresses follow:

```
tools.<integration>.<owner>.<connection>.<tool>
```

On each sandbox tool call, `makeExecutorToolInvoker` maps `tools.<path>(args)` to `executor.execute(address, args)` with credentials attached from the connection. Upstream MCP servers are registered through the MCP plugin (`packages/plugins/mcp`); their tools join the same catalog and are invoked through `execute`, not as additional MCP tools on Executor's endpoint.

<Note>
Credentials never reach the agent. The sandbox calls Executor's tool layer; Executor resolves the connection and attaches auth to the upstream request.
</Note>

## Policy enforcement

[Policies](/policies) apply per tool address during execution:

| Action | Effect at call time |
| --- | --- |
| `block` | Tool hidden from list; `ToolBlockedError` if invoked |
| `require_approval` | Execution pauses for elicitation; user or model must approve |
| `approve` | Call proceeds without an approval prompt |
| (none) | Falls through to integration default annotations |

Blocked tools surface in the sandbox as `{ ok: false, error: { code: "tool_blocked", ... } }`. Approval pauses return structured content with `executionId` and interaction metadata for `resume`.

## Transport and endpoints

<Tabs>
  <Tab title="Streamable HTTP">
    Executor serves MCP at `/mcp` on the local daemon (default port from install; the web UI Connect card shows the exact URL).

    <ParamField body="mcp-session-id" type="string">
      Required on `GET` for SSE. Omitted on the initial `POST` that opens a session.
    </ParamField>

    Allowed methods: `GET`, `POST`, `DELETE`, `OPTIONS`. Other methods return JSON-RPC `405`.

    Cloud deployments may scope the path to `/<organizationSlug>/mcp` (legacy `/<organizationId>/mcp` also accepted).

    <RequestExample>
```bash
curl -X POST http://127.0.0.1:17888/mcp \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'
```
    </RequestExample>

    Local `/api` and `/mcp` require a bearer token minted at server start. OAuth discovery routes are provider-specific and served outside the envelope.

  </Tab>
  <Tab title="Stdio">
    `executor mcp` starts an MCP server over stdio. It boots a local HTTP server in the background (for the web UI and browser approval URLs), then connects `StdioServerTransport` to `createExecutorMcpServer`.

    <CodeGroup>
```bash title="Default (model resume)"
executor mcp
```
```bash title="Browser approval"
executor mcp --elicitation-mode browser
```
```bash title="Scoped workspace"
executor mcp --scope /path/to/workspace
```
    </CodeGroup>

    Stdio mode redirects `stdout` to `stderr` so JSON-RPC on stdout stays clean for MCP clients.

  </Tab>
</Tabs>

## Elicitation modes

Control how paused executions (OAuth, approval gates) are resumed:

| Mode | How to set | `resume` behavior |
| --- | --- | --- |
| `model` (default) | Omit query param or `--elicitation-mode model` | Model calls `resume` with `action` and optional `content` |
| `browser` | `?elicitation_mode=browser` or `--elicitation-mode browser` | `execute` returns `approvalUrl`; user approves in browser; model calls `resume` to poll |
| `native` | `?elicitation_mode=native` | MCP SDK `elicitInput` during `execute`; no `resume` tool |

HTTP mode reads `?elicitation_mode=` from the session-open request. Legacy `?allow_model_resume=true` maps to `model`.

Browser approval URLs point at `/resume/<executionId>?mcp_session_id=<sessionId>`. The console posts the decision to `/api/mcp-sessions/<sessionId>/executions/<executionId>/resume`.

## Authentication

| Runtime | MCP auth |
| --- | --- |
| Local daemon | Bearer token on every `/mcp` and `/api` request (except OAuth callbacks and `/api/health`) |
| Self-host / Cloud | `McpAuthProvider` seam: OAuth bearer, RFC 9728 `WWW-Authenticate` on `401`, per-request principal resolution |
| Stdio (`executor mcp`) | No MCP-level auth; local server uses its own bearer internally |

The authenticated `Principal` (`accountId`, `organizationId`, roles) scopes connections, policies, and engine construction in multi-user hosts.

## Connect an MCP client

Use `npx add-mcp` to register Executor in Cursor, Claude Code, or OpenCode:

<CodeGroup>
```bash title="HTTP (local)"
npx add-mcp http://127.0.0.1:17888/mcp --transport http --name executor
```
```bash title="HTTP with bearer"
npx add-mcp http://127.0.0.1:17888/mcp --transport http --name executor \
  --header 'Authorization: Bearer <token>'
```
```bash title="Stdio"
npx add-mcp "executor mcp" --name executor
```
```bash title="Browser approval (HTTP)"
npx add-mcp 'http://127.0.0.1:17888/mcp?elicitation_mode=browser' --transport http --name executor
```
</CodeGroup>

The web UI Connect card generates these commands with the correct origin, token, and org slug. Restart the MCP client after adding Executor.

<Warning>
Adding or removing integrations does not require reconfiguring the MCP client. New tools appear in the `execute` description on the next session. An existing MCP session may need reconnecting to pick up an updated description.
</Warning>

## Proxy an upstream MCP server

<Steps>
  <Step title="Add the MCP integration">
    Register the upstream MCP server URL as an integration from the web UI or CLI. See [Add integrations](/add-integrations).
  </Step>
  <Step title="Create a connection">
    Bind credentials (API key, OAuth, or upstream bearer) to the integration. See [Configure credentials](/configure-credentials).
  </Step>
  <Step title="Verify the catalog">
    Confirm tools appear under the connection prefix:

```bash
executor tools sources
```
  </Step>
  <Step title="Call through execute">
    From an MCP client, use `execute` with sandbox code that calls `tools.<integration>.<owner>.<connection>.<tool>(args)`.
  </Step>
</Steps>

Upstream MCP tool annotations are preserved in persisted metadata and flow through invocation unchanged where the plugin supports them.

## Session lifecycle (HTTP)

```mermaid
sequenceDiagram
  participant C as MCP client
  participant E as /mcp envelope
  participant S as McpSessionStore
  participant T as Transport + McpServer

  C->>E: POST /mcp (no mcp-session-id)
  E->>E: authenticate
  E->>S: dispatch (create)
  S->>T: createExecutorMcpServer + connect
  T-->>C: Response + mcp-session-id

  C->>E: POST /mcp (mcp-session-id)
  E->>S: dispatch (forward)
  S->>T: handleRequest
  T-->>C: JSON-RPC result

  C->>E: DELETE /mcp (mcp-session-id)
  E->>S: dispatch (close)
  S->>T: dispose session
```

Cross-bearer reuse of a session id returns `403` (`MCP session does not belong to the current bearer`). Unknown session ids return `404`.

## Debugging and errors

<ParamField body="EXECUTOR_MCP_DEBUG" type="string">
Set to `1` or `true` to enable verbose MCP capability and elicitation logging on stderr.
</ParamField>

Infrastructure defects during `execute` return opaque `Internal tool error [<correlationId>]` with cause logged server-side. JSON-RPC errors use a canonical body: `{ "jsonrpc": "2.0", "error": { "code", "message" }, "id": null }`.

Common JSON-RPC codes from the envelope:

| HTTP | Code | Meaning |
| --- | --- | --- |
| 401 | -32001 | Unauthorized (missing or invalid bearer) |
| 403 | -32001 / -32003 | Forbidden or session ownership mismatch |
| 404 | -32001 | Session not found |
| 405 | -32001 | Method not allowed on `/mcp` |
| 500 | -32603 | Internal server error |

Paused executions expire after a few minutes or when the session runtime restarts. `resume` with a stale `executionId` returns `execution_not_found` with recovery guidance to re-run `execute`.

## Runtime differences

| Runtime | MCP entry | Session backend |
| --- | --- | --- |
| Local daemon | `http://127.0.0.1:<port>/mcp` | In-process (`createMcpRequestHandler`) |
| `executor mcp` | stdio | Shared engine across stdio session |
| Self-host | `/mcp` via `McpServingRoutes` | In-process `McpSessionStore` |
| Executor Cloud | `/<org-slug>/mcp` | Durable Object per session |

All variants build the per-session `McpServer` through `createExecutorMcpServer` and the execution stack (`makeMcpBuildServer` in multi-user hosts).

## Related pages

<CardGroup>
  <Card title="Connect MCP clients" href="/connect-mcp-clients">
    Wire Cursor, Claude Code, and OpenCode via stdio or streamable HTTP, including `add-mcp` setup.
  </Card>
  <Card title="Tools" href="/tools">
    Tool addresses, discovery, schema inspection, and how tools are produced per connection.
  </Card>
  <Card title="Executions" href="/executions">
    Code-mode execution, paused states, and MCP elicitation handling.
  </Card>
  <Card title="Policies" href="/policies">
    Per-tool allow, require-approval, and block actions with effective policy resolution.
  </Card>
  <Card title="Hosted cloud" href="/hosted-cloud">
    Shared MCP endpoint usage on Executor Cloud and how it differs from local.
  </Card>
</CardGroup>
