> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plungeai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP plane — /v1/mcp

> Two directions in one plane:


<!-- sources-of-truth: orchestration/api-gateway/openapi.ts, orchestration/api-gateway/routes/mcp.ts, orchestration/mcp-executor/mcp-executor.ts, docs/ONE-API-DEVELOPER-GUIDE-2.0.md | last-synced: 2026-09-24 -->
Two directions in one plane:

- **Inbound** — the platform *is* an MCP server: point any MCP client at it
  and get the whole platform (execute agents, workflows, discovery, …) as MCP
  tools.
- **Outbound** — the platform is an MCP *client* on your behalf: open a "run"
  against third-party MCP servers from the catalog and call their tools
  through one metered, tenant-isolated door.

Auth: `Authorization: Bearer ozk_YOUR_KEY` on every route here.

## Inbound — use the platform from any MCP client

Canonical Streamable-HTTP endpoints (both accept an `ozk_` bearer):

- `https://mcp.plungeai.com/v1` — the dedicated MCP host
- `https://api.plungeai.com/v1/mcp` — the same surface through the One API router

Claude Code:

```bash
claude mcp add plungeai --transport http https://mcp.plungeai.com/v1 \
  --header "Authorization: Bearer ozk_YOUR_KEY"
```

Generic `mcpServers` JSON (Cursor, clients with config files):

```json
{
  "mcpServers": {
    "plungeai": {
      "type": "http",
      "url": "https://mcp.plungeai.com/v1",
      "headers": { "Authorization": "Bearer ozk_YOUR_KEY" }
    }
  }
}
```

Per-client install steps (Claude Desktop, Cursor variants, VS Code, …) are the
per-editor `plungeai-in-*` skills' job (e.g. `plungeai-in-cursor`); this file
covers the HTTP surface.

Raw JSON-RPC 2.0 works too — `POST /v1/mcp` with the message as the body;
responses come back as JSON, or SSE for streamed tool calls. The
`Mcp-Session-Id` header is forwarded both ways — session-based flows must
replay the one the server returns.

### GET /v1/mcp/tools — what the platform exposes as MCP tools

Returns the standard JSON-RPC `tools/list` result (tool names like
`plungeai_execute_workflow`, `plungeai_execute_agent`, …). List it live —
the tool set evolves; never hardcode names or counts.

```bash
curl -s https://api.plungeai.com/v1/mcp/tools \
  -H "Authorization: Bearer ozk_YOUR_KEY"
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      { "name": "plungeai_execute_agent",
        "description": "Execute a platform agent with a prompt",
        "inputSchema": { "type": "object", "properties": { "...": "..." } } },
      { "name": "plungeai_execute_workflow", "description": "...", "inputSchema": { "...": "..." } }
    ]
  }
}
```

## Outbound — MCP runs

Lifecycle: **open a run → list/call its tools → close it**. Runs are
tenant-isolated (scoped to your key's identity) and **expire after 30 idle
minutes**; close them explicitly when done anyway.

Find `server_ids` in the catalog first:
`GET /v1/discovery/search?kind=mcp-servers&q=<what you need>` (`plungeai-discovery`)
— ids come from there, never from memory.

### POST /v1/mcp/runs — open a run

Body: `{"server_ids": ["<registry mcp-server card id>", ...]}`.

```bash
curl -s -X POST https://api.plungeai.com/v1/mcp/runs \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"server_ids": ["cloudflare-docs"]}'
```

HTTP **201**:

```json
{
  "run_id": "00000000-0000-4000-8000-000000000007",
  "tools": [
    { "name": "mcp__cloudflare-docs__search_cloudflare_documentation",
      "description": "Search Cloudflare documentation",
      "input_schema": { "type": "object", "properties": { "query": { "type": "string" } } } }
  ],
  "connected": ["cloudflare-docs"],
  "failed": [],
  "warnings": [],
  "skipped": []
}
```

Tool names are namespaced `mcp__<server>__<tool>`. Partial success is real:
check `connected` vs `failed` vs `skipped` — a run can open with some servers
up and others down (per-server errors are in `failed`).

Errors: `400 invalid_body` (missing/empty `server_ids`),
`502 no_servers_connected` — **none** of the requested servers came up; the
body carries per-server failures plus the same `skipped` array. A server that
just failed sits in a **60s cooldown**: re-opening immediately lists it in
`skipped` (not re-attempted) while other servers connect normally. Wait out
the cooldown before retrying it.

### GET /v1/mcp/runs/{id}/tools — the run's tool table

```bash
curl -s https://api.plungeai.com/v1/mcp/runs/00000000-0000-4000-8000-000000000005/tools \
  -H "Authorization: Bearer ozk_YOUR_KEY"
```

```json
{
  "tools": [ { "name": "mcp__cloudflare-docs__search_cloudflare_documentation",
               "description": "...", "input_schema": { "...": "..." } } ],
  "count": 1
}
```

An unknown or expired run id is **not a 404** here — it returns an empty tool
table. Empty tools on a run you opened a while ago means the 30-idle-minute
expiry hit: reopen the run.

### POST /v1/mcp/runs/{id}/call — call one tool

Body: `{"name": "<namespaced tool>", "args"?: object, "timeout_ms"?: integer}`.

```bash
curl -s -X POST https://api.plungeai.com/v1/mcp/runs/00000000-0000-4000-8000-000000000005/call \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "mcp__cloudflare-docs__search_cloudflare_documentation",
       "args": {"query": "durable objects alarms"}}'
```

```json
{ "content": "# Search results\n\n1. Durable Objects Alarms — ..." }
```

Errors: `400 invalid_body` (no `name`), `502 tool_call_failed` — the message
is always the fixed string `"MCP tool call failed"`; the upstream detail lives
in the error envelope's **`content`** extra. One retry with backoff is
reasonable, with one exception: a `content` of "no MCP servers are connected
for this run (inject was not called, or state expired)" means the run id is
unknown or idle-expired — **reopen the run**, don't debug the tool name.

### DELETE /v1/mcp/runs/{id} — close a run

Idempotent — deleting an already-gone run still returns success.

```bash
curl -s -X DELETE https://api.plungeai.com/v1/mcp/runs/00000000-0000-4000-8000-000000000005 \
  -H "Authorization: Bearer ozk_YOUR_KEY"
```

```json
{ "success": true }
```

## Full outbound pattern (TypeScript, plain fetch)

```ts
const BASE = 'https://api.plungeai.com'
const H = { Authorization: `Bearer ${process.env.PLUNGEAI_API_KEY}`, // ozk_YOUR_KEY
            'Content-Type': 'application/json' }

// 1. open
const run = await (await fetch(`${BASE}/v1/mcp/runs`, {
  method: 'POST', headers: H,
  body: JSON.stringify({ server_ids: ['cloudflare-docs'] }),
})).json()
if (!run.connected?.length) throw new Error(JSON.stringify(run.failed))

// 2. call (tool name taken from run.tools — never hardcoded)
const tool = run.tools[0].name
const res = await (await fetch(`${BASE}/v1/mcp/runs/${run.run_id}/call`, {
  method: 'POST', headers: H,
  body: JSON.stringify({ name: tool, args: { query: 'workers kv limits' } }),
})).json()
console.log(res.content)

// 3. close
await fetch(`${BASE}/v1/mcp/runs/${run.run_id}`, { method: 'DELETE', headers: H })
```
