plungeai-results-traces
MCP plane — /v1/mcp
Two directions in one plane:
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 hosthttps://api.plungeai.com/v1/mcp— the same surface through the One API router
Claude Code:
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):
{
"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.
curl -s https://api.plungeai.com/v1/mcp/tools \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"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>", ...]}.
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:
{
"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
curl -s https://api.plungeai.com/v1/mcp/runs/00000000-0000-4000-8000-000000000005/tools \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"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}.
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"}}'{ "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.
curl -s -X DELETE https://api.plungeai.com/v1/mcp/runs/00000000-0000-4000-8000-000000000005 \
-H "Authorization: Bearer ozk_YOUR_KEY"{ "success": true }Full outbound pattern (TypeScript, plain fetch)
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 })