For AI agents: a documentation index is available at https://www.plungeai.com/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://www.plungeai.com/docs/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

Source: orchestration/mcp-gateway/index.ts, server.ts, session-do.ts, shared.ts, tool-outcome.ts, execution.ts, execution-do.ts, leg-state.ts, conversation.ts, chat.ts, tools.ts, tool-exec.ts, extras.ts, orchestration/api-gateway/routes/mcp.ts, orchestration/api-gateway/lib/auth.ts, and the MCP TypeScript SDK 1.29.0 (node_modules/@modelcontextprotocol/sdk: server/index.js, server/mcp.js, server/webStandardStreamableHttp.js, shared/protocol.js, types.js).

The PlungeAI MCP server speaks JSON-RPC 2.0 over Streamable HTTP at one endpoint:

POST https://mcp.plungeai.com/v1

https://api.plungeai.com/v1/mcp is the same server, reached through the One API router. The differences at the edge are listed in 4.11. This chapter is for people writing their own client or debugging one. If you use Claude Code, Cursor or another MCP client, the client does all of this for you (Connecting clients).

4.1 How a request flows

The big picture:

you type a sentence
   └─ your client reads it and decides which tool to call
        └─ POST https://mcp.plungeai.com/v1   (one JSON-RPC message)
             └─ the platform: registry agents, the CNL engine, your saved workflows

Every POST passes the same gates, in this order (index.ts). The first gate that fails answers, and nothing after it runs:

1. HTTP method  GET / DELETE without Mcp-Session-Id         → 405 -32000 "Method not allowed."
2. Host         not mcp.plungeai.com                          → 403 -32000 "Invalid Host header: …"
3. Size         Content-Length over 1 MiB                     → 413 -32600
4. Auth         X-API-Key, or Authorization: Bearer ozk_…     → 401 -32001 (failed auth is metered per IP)
5. Body         read with a hard 1 MiB cap                    → 413 -32600
   Rate limit   only when the body carries a tools/call       → 429 -32000 + Retry-After
6. Parse        body is not JSON                              → 400 -32700 "Parse error"
7. Pre-screen   single tools/call: unknown tool / fenced tool → 200 -32602
                single tools/call: arguments fail the schema  → 200 result, outcome needs_input
                single initialize: bad params                 → 200 -32602 "Invalid params: …"
8. Session      Mcp-Session-Id present, or a sessions key initializing → session Durable Object (4.5)
9. SDK          a fresh MCP server instance answers the message; tools/call streams as SSE

What happens after gate 9 depends on the tool. A tool that runs something (a workflow, an agent, a mission) writes a row to your execution history with source: mcp and broadcasts it to your other apps. That is the same account and the same history Studio shows. Every authenticated request, including protocol traffic such as initialize and resources/read, also lands in your activity log (13.10).

4.2 Endpoint, paths, headers and methods

ItemValue
EndpointPOST https://mcp.plungeai.com/v1. /v1/ and the pre-2026-09-20 path /mcp answer identically.
Other hostsAny other Host gets 403.
OPTIONSCORS preflight, answered for any origin: 204 with Access-Control-Allow-Origin: *, allowed methods GET, POST, DELETE, OPTIONS, allowed headers Content-Type, Authorization, Mcp-Session-Id, MCP-Protocol-Version, X-API-Key, exposed header Mcp-Session-Id, max-age 86400 (live-verified 2026-09-24).
GET, DELETE405 with Allow: POST, OPTIONS. They are only accepted with an Mcp-Session-Id (opt-in sessions, 4.5).
Unknown path404 {"error":"Not found. MCP endpoint: POST /v1 (Streamable HTTP)."} (not JSON-RPC).
No-auth info routesGET /v1/info (server name, version, protocol version, capabilities), GET /health (dependency probes; 503 when one fails).

Request headers:

HeaderRequiredWhat the server does with it
Authorization: Bearer $PLUNGE_API_KEY or X-API-Key: $PLUNGE_API_KEYyesYour ozk_ key (Authentication). Bearer tokens that do not start with ozk_ are not accepted.
Content-Type: application/jsonyes, unless Accept is incompleteWhen Accept lacks one of the two types (below), the server rewrites both headers for you. With a complete Accept and another Content-Type, the SDK answers 415 -32000 "Unsupported Media Type: Content-Type must be application/json".
Accept: application/json, text/event-streamrecommendedThe spec requires both types. A request without them is not rejected: the server fills them in, so a bare curl works.
MCP-Protocol-VersionoptionalOn requests after initialize, an unsupported value gets 400 (4.3). It is not checked on initialize itself, and a plain tools/list answered from the server's cache gets 200 whatever the value. Send a supported version, or leave the header out.
Mcp-Session-Idonly for opt-in sessionsSending one routes the request to the session host. An unknown id gets 404 (4.5).

Response types:

RequestResponse
tools/call (single, or any batch containing one)200, Content-Type: text/event-stream (4.6)
Every other request200, Content-Type: application/json on the default stateless path. On an opt-in session (4.5) every request, initialize included, is answered as SSE.
Only notifications (no id), e.g. notifications/initialized202 Accepted, empty body
An edge gate failed (401, 403, 405, 413, 429, 400)that status, a JSON-RPC error body (13.6)

Server info without a key:

curl -s https://mcp.plungeai.com/v1/info

A GET on the endpoint is refused because the server offers no standalone stream:

curl -s -i https://mcp.plungeai.com/v1

4.3 Protocol version and initialize

The server implements MCP revision 2025-11-25 (PROTOCOL_VERSION in server.ts). It negotiates exactly as the SDK does (_oninitialize): if the protocolVersion your client sends is one of 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07, the server answers with that version. Anything else gets 2025-11-25, and your client decides whether it can speak it. /health describes this as "negotiates down to 2024-11-05".

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

Ask for an older revision and you get it back:

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

The initialize result:

FieldValue
protocolVersionthe negotiated version (above)
serverInfo{ "name": "plungeai.com", "version": "2.7.0" } (SERVER_NAME, SERVER_VERSION; the version moves with deploys)
capabilitieslogging: {}, resources: {}, prompts: {}, tools: { listChanged: true }. The listChanged flag comes from the SDK; the stateless server never actually sends a list_changed notification (4.5).
instructionsthe server-level usage guide, injected into your client's context

The instructions text is the one place that teaches every client how to use the platform. In short, it tells the model to:

  • relay every successful tool output verbatim and in full. The only parts meant for the model are the guidance tails: execution-id footers, poll hints and ⏸ blocks;
  • treat "my agents" as the user's saved workflows (plungeai_list_workflows). plungeai_list_agents is the registry of building blocks;
  • discover first: search the registry in natural language, fetch a full card before first use, and take agent ids only from live results (the catalog is active-only);
  • run structured agents with the triad plungeai_list_agents → plungeai_get_tool_contract → plungeai_execute_tool, follow the outcome's remediation, never retry blind, and never approve a gated action on its own;
  • use mode:"async" only for long runs and poll plungeai_get_workflow_status;
  • stop at every ⏸ AWAITING USER / AWAITING USER APPROVAL block, relay it, then call plungeai_continue;
  • pass user_request, the user's own words, on every tool call. Every tool accepts it, and the platform uses it for context, routing and your activity log (13.10).

initialize params are checked before the SDK sees them. A malformed request (for example a numeric protocolVersion and no clientInfo) gets -32602 naming the bad fields: {"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params: protocolVersion, clientInfo"},"id":1} (HTTP 200). This check covers single-message requests only.

After initialize, a client sends notifications/initialized. It has no id, so the server answers 202 with no body:

curl -s -o /dev/null -w '%{http_code}\n' https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

The MCP-Protocol-Version header. You may send it on requests after initialize. The SDK accepts any of the five supported versions. An unsupported value gets 400 -32000 "Bad Request: Unsupported protocol version: <v> (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)". Two exceptions: initialize itself is never checked, and a single uncursored tools/list that a warm server instance answers from its cache (index.ts) returns 200 before the check runs, so the same bad header can get 200 or 400 depending on cache state. Leaving it out is fine.

4.4 JSON-RPC methods

Every method the server answers for a normal key (server.ts, SDK Protocol and McpServer):

MethodSupportedReturns
initializesupported4.3
notifications/initializedsupported202, no body
pingsupported{}
tools/listsupportedthe 22 plungeai_* tools in catalog order, or only the tools your key is fenced to. No pagination: there is no nextCursor, and a cursor param is ignored (full list again). Details in 4.10 and the Tool reference.
tools/callsupportedstreamed as SSE (4.6)
resources/listsupported5 resources (Prompts & resources)
resources/templates/listsupported3 URI templates
resources/readsupportedthe resource's text, or -32002 for an unknown URI
prompts/listsupported1 prompt, plungeai
prompts/getsupportedthe prompt's message, or -32602 for an unknown name
logging/setLevelpartialreturns {}, but the stateless server keeps no state between requests, so the level is not applied to later calls. Progress fallback lines are always sent at level info (4.6).
notifications/cancelledignored202. The notification arrives in a new request with its own server instance, which cannot reach the call in flight. To stop a run, use plungeai_executions {"action":"cancel"} (Runs, results & follow-ups).
resources/subscribe, resources/unsubscribe, completion/completerejected-32601 "Method not found". They exist only on the conformance-test keys.
any other methodrejected-32601 "Method not found"

The server never sends requests to your client. Sampling, elicitation and roots are not used by any production tool.

A quick liveness check:

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":7,"method":"ping"}'

Batches. A JSON array of messages is accepted and answered as an array. The SDK answers each message on its own:

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '[{"jsonrpc":"2.0","id":1,"method":"ping"},{"jsonrpc":"2.0","id":2,"method":"prompts/list"}]'

Batch caveats (index.ts):

  • The pre-screen in gate 7 runs for single messages only. In a batch, an unknown or fenced tool comes back as the SDK's own isError tool result (MCP error -32602: Tool <name> not found), and bad arguments come back as the SDK's Input validation error: … text. You get no -32602 envelope and no needs_input outcome. Fenced tools are still never run: they are not registered on your server instance.
  • A batch that contains initialize together with other messages gets 400 -32600 "Invalid Request: Only one initialization request is allowed".
  • A batch containing a tools/call is answered as SSE.

4.5 Stateless by default, opt-in sessions

Stateless is the default. Each POST builds a fresh server instance, answers it, and forgets it (server.ts: "Built fresh per request"). No Mcp-Session-Id is issued, and you do not need one. Each request is self-contained: tools/list or tools/call work without an earlier initialize in the same connection. Clients still send initialize once to learn the version, capabilities and instructions.

What statelessness means in practice:

FeatureOn the stateless path
Server-initiated stream (GET)not offered: 405
notifications/tools/list_changednever sent. If a tool seems missing after a platform update, reconnect your client so it re-reads tools/list.
logging/setLevelaccepted, not persisted
notifications/cancelledcannot reach an in-flight call. Use plungeai_executions cancel.
Resumable SSE (Last-Event-ID)not available. A dropped tools/call stream is not replayed. The run itself keeps going and can be read with plungeai_get_result.
Sampling, elicitationnot available (and no production tool uses them)

Opt-in sessions (session-do.ts). A key minted with sessions: true gets a stateful session. This is an operator setting: self-service keys from Dashboard → One API → Keys do not carry it.

  • initialize returns an Mcp-Session-Id response header. Send it on every later request.
  • On a session, every POST that carries a request is answered as text/event-stream, initialize and tools/list included. When the request speaks 2025-11-25 (the initialize param, or the MCP-Protocol-Version header after it), the stream opens with a priming event (id:, retry: 1000, an empty data:). Read the Mcp-Session-Id response header, then parse the data: lines as in 4.6.
  • GET with the header opens the standalone SSE stream. It is resumable with Last-Event-ID; the server sends a 1000 ms retry hint and stores up to 1,000 replay events per session.
  • DELETE with the header ends the session.
  • Sessions expire after 10 minutes without a request. The window slides with each request.
  • A session is bound to the key that created it. The same id presented by another key, an expired id, an id that never existed, or one longer than 128 characters all get 404 -32001 "Session not found". Per the spec, re-initialize to get a new one.
  • A second initialize on a live session gets 400 -32600 "Invalid Request: Server already initialized".
  • Sessions are available on https://mcp.plungeai.com/v1 only (4.11).

If your client sends an Mcp-Session-Id it picked up elsewhere, the request goes to the session host and gets 404 Session not found. Drop the header.

4.6 tools/call over SSE: progress and keepalive

A tools/call response is a Server-Sent Events stream. Each frame is event: message, then one data: line holding a JSON-RPC message, then a blank line. Progress notifications (if any) come first. The last frame is the JSON-RPC response, and then the stream closes. With curl, keep the data: lines:

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"plungeai_whoami","arguments":{"user_request":"who am I"}}}' \
  | sed -n 's/^data: //p'

Some tools/call answers are plain JSON instead of SSE: the gate 7 pre-screen answers (unknown tool, fenced tool, arguments that fail the schema) and every edge error (401, 413, 429). A robust client checks Content-Type before parsing.

Live status: progress notifications

While a tool runs, the server streams what it is doing. There is one channel per call, never both (server.ts wrap):

Your tools/callChannelParams
carries params._meta.progressToken (Claude Code and Claude Desktop send one)notifications/progressprogressToken, progress (strictly increasing), total (only when a raw done/total pair passes through unchanged), message (the human-readable line)
no progress tokennotifications/message (logging)level: "info", logger: "<tool name>", data: "<the same line>"

progress is a sequence, not a percentage. The server keeps it strictly increasing as the spec requires, and the readable "5/18" lives in message. Notifications stop the moment the call settles.

A call that asks for progress (runs a model, costs credits, so it is not auto-verified):

curl -sN https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"_meta":{"progressToken":"p1"},"name":"plungeai_execute_agent","arguments":{"user_request":"one-sentence answer","agent":"one-agent","prompt":"In one sentence: what is a Durable Object?"}}}'

Frame shape while it runs (the shape server.ts builds; the numbers and text vary):

event: message
data: {"method":"notifications/progress","params":{"progressToken":"p1","progress":3,"message":"⚙️ Agents working — 0/1 agents complete · 1 active (one-agent)"},"jsonrpc":"2.0"}

The lines you will see, by tool (execution.ts, conversation.ts, chat.ts):

ToolStatus lines
workflow, agent, tool and mission runs⚙️ Agents working — 5/18 agents complete · ✓ brave-agent · 13 active (tavily-agent, exa-agent…) · ✍️ Generating answer · ✍️ Writing (3,516 chars) — …<the last ~72 characters of the answer> · ⚙️ one-agent still working — 45s elapsed… · ✅ Complete — 18 agents in 24.6s
plungeai_followup🔎 Looking up the run and checking for an open session… · 💬 <agent> working — continuing the conversation… or 📚 No open session — loading the run's stored result as context… · ⏳ Collecting the answer… · 💾 Saving this turn to the conversation thread… · ✅ Complete — continued the existing session / ✅ Complete
plungeai_continue🔎 Loading the paused conversation… · 🔓 Approval received · 💾 Saving this turn… · ✅ Complete
plungeai_build_workflow📖 Loading the existing workflow… · 🧠 Generating CNL YAML — the builder picks real agents, validates and self-repairs (30-60s)… · 🔍 Validating the generated workflow (<n> chars)… · 💾 Saving "<name>" and syncing to all apps… · ✅ Complete
plungeai_chatThinking… · Streaming… <n> chars
any tool, every 15 s⏳ Working — <n>s elapsed

The 15-second keepalive runs for every tool call. It keeps the SSE stream from going idle long enough for an intermediary to cut it. The ✍️ Writing lines carry only the tail of the answer as it is produced. The full answer arrives in the final response.

Whether you see these lines depends on your client. Some render them under the running tool, some ignore both channels. The work and the result are identical either way. mode:"async" runs return at once, so they have no live feed (4.7).

4.7 Async mode

Four tools can run in the background. The call returns an execution id at once instead of waiting for the result:

ToolmodeDefault
plungeai_execute_workflowsync | asyncsync
plungeai_execute_toolsync | asyncsync
plungeai_run_missionsync | asyncasync (anything but "sync")
plungeai_learn (action: learn)sync | asyncasync (anything but "sync")

plungeai_execute_agent has no mode.

The immediate answer is an ok outcome. Its structuredContent has status: "ok", execution_id, and a summary that says the run started in the background and tells the client to poll plungeai_get_workflow_status. The wording differs per tool: "Started execution …" (plungeai_execute_workflow), "Started <name> …" (plungeai_execute_tool), "Started mission …" (plungeai_run_mission), "Started distilling …" (plungeai_learn). An ok outcome with execution_id and no result means an async start, or a sync run whose output was not yet available ("… completed (execution: …) but results not yet available"). Either way, poll plungeai_get_workflow_status, then call plungeai_get_result. Don't branch on the summary text. plungeai_execute_tool also sets agent_id and operation. The Markdown text ends with a footer the client acts on:

---
execution: `<execution id>` — use this id for plungeai_get_result / plungeai_followup on this run.

Then:

  1. Poll plungeai_get_workflow_status with the id. Its structuredContent.status reports the run, and continuation is non-null when the run paused for your approval or an answer.
  2. When it reads completed (or incomplete), fetch the output with plungeai_get_result (Runs, results & follow-ups).

A background run (runs a workflow, costs credits, not auto-verified):

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"plungeai_execute_workflow","arguments":{"user_request":"research in the background","mode":"async","format":"json","workflow_yaml":"name: bg-demo\ntasks:\n  - id: answer\n    agent: one-agent\n    prompt: |\n      In two sentences: what is edge computing?\n"}}}' \
  | sed -n 's/^data: //p'

How background runs are hosted (execution-do.ts, leg-state.ts, index.ts):

  • Each run is held by its own Durable Object in legs of up to 10 minutes (the Ten-Minute Relay). A long run hands over at a turn boundary and resumes in the next leg from stored task results and checkpoints.
  • A leg that is killed (deploy, host restart) is re-entered by a watchdog 12 minutes after it started. If it is killed 3 times in a row without writing a checkpoint, the run fails with "Run was killed 3 times in a row without yielding a checkpoint…".
  • A running row with no heartbeat for 16 minutes (960 s) is marked failed with Interrupted (run did not complete). A cron does this every 15 minutes, and a status poll does it at once. Its cost is left pending, not unpriced: the cost worker then prices whatever the run's agents recorded before it stopped.

When to use it: the tool descriptions say async is for runs over about 3 minutes. Most workflows finish in seconds in sync. Claude Desktop cuts a single tool call at about 4 minutes, so use async there for anything long.

4.8 Output formats: Markdown by default, JSON on request

Results come back as Markdown, the format an AI reads best: tables, headings, links, step indexes, conversation threads. The server's instructions tell clients to show it verbatim.

If your client needs machine-parseable output, add "format": "json" to a result-bearing tool. The text becomes a JSON document, and the same object is attached as structuredContent (tool-outcome.ts renderAs). Without format (or with "markdown"), nothing changes. isError is the same in both forms.

Toolformat:"json" returns
plungeai_whoami{ user, auth_type, tier, key_label, key_id, rate_window: { minute_used, day_used } | null, server }
plungeai_list_workflows{ workflows: [ { id, name, description, kind, folder } ] }. Without filters this is the recent list; the folder summary line is Markdown-only.
plungeai_executionslist → { executions: [ { id, input, workflow_name, status, start_time, duration, source } ] }; conversation → { execution_id, messages }; get → the status object (4.9); output, cancel, delete → { content: "<markdown>" }
plungeai_list_agents{ content: "<registry markdown>" } (typed rows are not available yet)
plungeai_get_result{ content: "<markdown>" }, or the outcome object when the answer is guidance (e.g. an unknown id)
plungeai_execute_workflow, plungeai_execute_agent, plungeai_execute_tool, plungeai_get_tool_contractthe outcome object (4.9)

Tools without a format parameter ignore it. The server drops unknown arguments before the tool runs.

curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"plungeai_whoami","arguments":{"user_request":"who am I, as JSON","format":"json"}}}' \
  | sed -n 's/^data: //p'

Content annotations. Successful Markdown results built as plain text carry annotations: { "audience": ["user","assistant"], "priority": 0.9 } (shared.ts), a hint that the text is for the user to read as-is. Outcome-envelope results and error text carry no annotations, and neither does format:"json" output.

4.9 outputSchema and structuredContent

Seven tools declare an outputSchema in tools/list. For these, structuredContent is always present on a non-error result, and it matches the schema:

ToolsSchema
plungeai_execute_workflow, plungeai_execute_agent, plungeai_get_tool_contract, plungeai_execute_tool, plungeai_run_mission, plungeai_learnthe outcome envelope (below)
plungeai_get_workflow_statusthe status object (below)

The outcome envelope (ToolOutcomeSchema, deliberately loose so it can grow fields):

FieldTypeNotes
statusok | needs_input | needs_connection | needs_api_key | needs_approval | unavailable | errorthe outcome (Errors, feedback & limits)
summarystringalways present
agent_id, operationstringwhen a specific agent answered
resultstringthe run's output (on ok, or the partial output next to needs_approval)
execution_idstringthe run's id, for plungeai_get_result / plungeai_followup
missingstring[]absent required fields (needs_input)
warningsstring[]visible notes, e.g. "prose mapped: …"
remediation{ message, actions[] }what to do next (13.2)
contractobjectthe tool contract (from plungeai_get_tool_contract, and appended to needs_input)

The status object (plungeai_get_workflow_status):

FieldType
execution_idstring
statusstring (running, completed, incomplete, failed, cancelled, …)
error_messagestring | null (set when the run failed)
workflow_name, started_at (ISO), final_task_idstring | null
duration_msnumber | null
continuationnull, or { status, agent, question: string | null, pending_action: { summary, price? } | null } when paused

isError follows the spec: it is true only for a terminal error outcome and for plain tool failures. The guidance outcomes (needs_*, unavailable) are answers your client acts on, so they carry isError: false. The SDK skips outputSchema validation on isError results, which is why an error answer from these tools may carry plain text only.

Tools without an outputSchema still attach structuredContent when you ask for format:"json" (4.8).

4.10 Tool annotations, read-only hints and metadata

Six tools carry annotations: { "readOnlyHint": true }. They only read. The hint is for your client, for example to run them without a confirmation prompt:

plungeai_get_tool_contract · plungeai_list_agents · plungeai_list_workflows · plungeai_get_result · plungeai_get_workflow_status · plungeai_whoami

The other 14 tools carry no annotations: no destructiveHint, idempotentHint or openWorldHint, so your client applies its own defaults. Some of them change state or spend: the execute and mission tools run work, plungeai_executions can delete or cancel, and plungeai_schedule, plungeai_workflow, plungeai_memory remember and plungeai_templates use write.

Other tool metadata in tools/list:

  • No title is set. Clients display the name.
  • The SDK marks every tool execution: { taskSupport: "forbidden" }: MCP task-augmented calls are not supported. Use async mode for long work.
  • Every tool's inputSchema has an optional user_request string (up to 4,000 characters) for the user's own words.
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

4.11 The two endpoints at the transport level

https://api.plungeai.com/v1/mcp forwards your JSON-RPC body (plus Accept, Content-Type and Mcp-Session-Id) to this server with your own ozk_ key, which this server validates again (orchestration/api-gateway/routes/mcp.ts). The tools and their answers are identical. The edge is not:

https://mcp.plungeai.com/v1https://api.plungeai.com/v1/mcp
MethodsPOST, OPTIONS (+ GET/DELETE with a session)POST only
Auth failure401 JSON-RPC -32001 + WWW-Authenticate401 One API envelope {"error":{"code":"unauthorized",…}} + X-Error-Code: unauthorized
Rate limit countsonly requests carrying a tools/callonly requests carrying a tools/call, counted by this server in the same per-account counter
Per-key restrictions: tool fence, per-key rate override, sessionsappliedapplied. Your key is forwarded and this server validates it again. Sessions need GET/DELETE, which this route does not offer: open sessions on mcp.plungeai.com.
plungeai_whoami showsapi-key, your tier and key labelthe same
Other routes/log, /v1/info, /health, /install, /docsGET /v1/mcp/tools (a JSON-RPC tools/list without writing JSON-RPC, One API guide (opens in a new tab))

Use https://mcp.plungeai.com/v1 when you need sessions. The unauthenticated difference, live-verified 2026-09-24:

curl -s -X POST https://mcp.plungeai.com/v1 -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
echo
curl -s -i -X POST https://api.plungeai.com/v1/mcp -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.