In your tool
plungeai-in-bolt
Connect Bolt.new to PlungeAI (Ocean Studio) over MCP — the Connectors → Custom MCP server form, the all-tools/all-projects toggle behavior, keeping the shipped app's key server-side, and a plungeai_whoami + plungeai_list_agents verify. Use when the user is in Bolt.new and says connect / install / set up PlungeAI, mentions Custom MCP server, or a deployed Bolt app 401s against PlungeAI. For another builder use its plungeai-in-<tool> skill; for an unlisted but MCP-capable client use plungeai-mcp-setup's generic client config.
Download zip (opens in a new tab) · View raw SKILL.md (opens in a new tab)
Bolt is a hosted builder: there is no local config file and no JSON entry anywhere — the connector form is the only surface, and the key lives only in its credential.
Prerequisites
- An
ozk_key: Dashboard → One API → Keys (https://dashboard.plungeai.com (opens in a new tab)) — self-service, shown once, copy it now.
Connect (once)
From the Bolt homepage chatbox: click the + icon → Connectors → Manage connectors → Custom MCP server, then fill:
| Field | Value |
|---|---|
| Name | PlungeAI |
| URL | https://mcp.plungeai.com/v1 |
| Transport type | HTTP (use SSE only if a server's docs say so — PlungeAI's is HTTP) |
| Authentication | select API key and enter the ozk_ key (or MCP OAuth — not available for PlungeAI yet) |
Bolt does not document which header the API-key field sends; the PlungeAI
server accepts both X-API-Key: ozk_YOUR_KEY and
Authorization: Bearer ozk_YOUR_KEY, so the raw key works — confirm with
plungeai_whoami (Verify below).
Verify
- Builder chat: "use plungeai_whoami to confirm my identity" → an identity card.
- Generated app (server side):
curl -H "Authorization: Bearer ozk_YOUR_KEY" https://api.plungeai.com/v1/agents→ HTTP 200 JSON. - "use plungeai_list_agents to search 'web search'" → live results from the active agent catalog.
Quirks — the toggles that bite
- Auto-enable for all projects controls whether the connector turns on automatically in each new project — existing projects enable it from the same Connectors menu.
- Adding a connector turns on all its tools by default, and the tool list is global — you can't turn tools on/off per project. Trim the tool list once, globally, if the builder gets tool-choice-noisy.
- Tools missing mid-session → Manage connectors: check the connector is enabled for this project and its tool toggles are on, then retry.
- The connector credential authenticates the BUILDER only. The shipped app
needs its own wiring: a server function holding
PLUNGEAI_API_KEYas an env var that proxies the One API. An app that "worked in preview" but 401s when deployed usually shipped without the env var set. The key must never appear in the client bundle or a browserfetch. - Long CNL workflows can exceed a builder-chat step budget — prefer async
mode (
plungeai_execute_workflowwithmode: "async"+plungeai_get_workflow_status).
What the shipped app needs to know (One API 2.3.0)
The app's server function is the only caller of the One API: it reads
PLUNGEAI_API_KEY from its env and sets the Authorization header itself (no
connector attaches auth for the shipped app). Give the builder this block in
the build prompt so it implements real routes instead of guessing.
The minimal saved-workflow body (workflow: / tasks: YAML) is in plungeai-api-setup.
PlungeAI One API 2.3.0. Base URL https://api.plungeai.com. Spec: GET /v1/openapi.json (no auth) - never invent a route.
AUTH: Authorization: Bearer ozk_YOUR_KEY on every call; key in an env var, server side only. When a connector attaches auth, NEVER add your own Authorization header.
ME: GET /v1/whoami -> {user_id, via, tier, key_label}. GET /v1/key -> {data:{label, limit, usage, rate_limit}}.
FIND (never from memory): GET /v1/discovery/search?q=<capability>&kind=agents -> {cards} (without kind, skill cards come first). POST /v1/discovery/resolve {task}. GET /v1/agents. GET /v1/tools. GET /v1/tools/{id} = the contract.
DOOR 1, one agent or tool: POST /v1/agents/{id}/execute {prompt|input, sync?, model?} -> 200 {content, workflow_id, task_id, request_id}.
POST /v1/tools/{id}/execute {operation, params} -> {ok, content, outcome}; 422 invalid_params echoes the contract.
DOOR 2, saved workflow or bot: POST /v1/workflows/{id}/execute {input?, inputs?} -> {success, workflow_id, final_task_id, request_id, content}, synchronous.
POST /v1/workflows/{id}/execute-stream = SSE. Inline YAML: POST /v1/workflows/execute. No content? GET /v1/workflows/results/{workflowId}/{taskId} (taskId = final_task_id).
DOOR 3, Agent Call: POST /v1/execute, the body is the program: {agent:"<saved id>[@n]", input} or {workflow:{...}} or {tasks:[...]}. dry_run:true plans without running;
on_behalf_of:"<customer id>" uses that customer's connections.
ASYNC: POST /v1/agents/{id}/execute {sync:false} -> 202 {workflow_id, task_id, request_id}; redeem GET /v1/agents/results/{workflowId}/{taskId} -> {content}; 404 not_ready = poll, 2s+.
EXECUTIONS: the run id is the body's workflow_id (also the x-execution-id header, and your x-trace-id UUID if you sent one; replaying one is 409 duplicate_execution_id).
GET /v1/executions (source, status, workflow_id). GET /v1/executions/{id}: running | completed | incomplete | failed | cancelled | needs_input | needs_approval.
GET /v1/executions/{id}/output -> {content, steps}. GET /v1/executions/{id}/files. GET /v1/executions/{id}/conversation. DELETE /v1/executions/{id}. GET /v1/executions/{id}/stream = SSE.
POST /v1/executions/{id}/continue {answer | approve, allow_scope: once|run|always}. POST /v1/executions/{id}/followup {text}. POST /v1/executions/{id}/cancel.
POST /v1/executions/{id}/steer {text} queues a message for a live run (202), else 409 execution_finished.
WORKFLOWS: GET|POST /v1/workflows. GET|PATCH|DELETE /v1/workflows/{id}. GET|POST /v1/workflows/{id}/versions. POST /v1/workflows/{id}/versions/{versionId}/restore.
Save needs {name, kind, yaml}; kind = workflow|agent|bot|campaign. GET /v1/workflows lists every kind unless ?kind=bot (or campaign). 422 invalid_workflow lists errors, writes nothing.
SCHEDULES: POST /v1/schedules {name, schedule, workflow_id, timezone?, parameters?}; schedule = a cron, or "@once" with run_at (ISO-8601 WITH a UTC offset, in the future);
400 run_at_required | run_at_past. GET /v1/schedules. GET|PATCH|DELETE /v1/schedules/{id}. GET /v1/schedules/{id}/runs (execution_id per run).
POST /v1/schedules/{id}/run. POST /v1/schedules/{id}/pause. POST /v1/schedules/{id}/resume. POST /v1/schedules/{id}/wake {note?, in?} fires it once, now or later.
TRIGGERS (every call names schedule_id; a secret is shown once): GET|POST /v1/triggers/webhook. DELETE /v1/triggers/webhook/{token}. POST /v1/triggers/webhook/{token}/rotate.
The same four under /v1/triggers/email {allowed_from}. GET|POST /v1/triggers/hooks {url, events}. DELETE /v1/triggers/hooks/{id}.
PERMISSIONS: GET|PUT /v1/workflows/{id}/permissions {classes:{send|write|pay|delete: allow|ask|deny|hand_off}, locks, budget_usd_run, budget_usd_month}; locks need a team admin (403 lock_admin_required).
BOT DATA: GET /v1/workflows/{id}/workspace-files. GET /v1/workflows/{id}/workspace-files/download?path=. GET /v1/workflows/{id}/inbox. GET|POST /v1/workflows/{id}/bindings {channel, sender_id}.
DELETE /v1/workflows/{id}/bindings/{channel}/{sender_id}. GET /v1/channels. POST /v1/channels/pair {channel}.
GET|POST /v1/bot-groups {name, members = 2-6 of your bot ids}. PUT|DELETE /v1/bot-groups/{id}. GET /v1/bot-groups/{id}/thread.
MEMORY: GET /v1/memory/recall?q=. POST /v1/memory/remember {text, scope: memory|user}. POST /v1/memory/forget {id | text}. GET /v1/memory/runs?q= (q required). GET /v1/memory/runs/{id}.
SKILLS: GET /v1/skills. POST /v1/skills/learn {name, body} saves the body you send (it does not distill a URL). DELETE /v1/skills/{id}.
TEMPLATES: GET /v1/templates?kind=workflow|agent|bot. GET /v1/templates/{id}. POST /v1/templates/{id}/use {name?} -> 201 {workflow}.
CONNECTIONS: GET /v1/connections. POST /v1/connections/{provider} {api_key, on_behalf_of?}. DELETE /v1/connections/{provider}. POST /v1/connections/{provider}/link -> {url, expires_at}.
MODELS (OpenAI-shaped, Bearer only): GET /v1/models. POST /v1/chat/completions. POST /v1/embeddings.
TRACES: GET /v1/traces/{id}, id = the run's workflow_id.
LISTS: {workflows|executions|schedules|cards:[...], total, limit, offset, has_more}; POST /v1/schedules -> {schedule}.
ERRORS: {"error":{"code","message"}} plus an X-Error-Code header. 400 validation; 401 unauthorized; 403 refused | agent_not_active; 404 <thing>_not_found (a stranger's id too);
409 approval_required, or not_paused | execution_finished | duplicate_execution_id | name_taken; 422 invalid_params; 429 rate_limited + Retry-After; 502/503 retry once.
424 connection_required | credential_required carries error.connect {provider, method, link}: connect via POST /v1/connections/{provider} or its /link.
FENCES: 403 refused is final - surface it, never retry. 409 approval_required is a human boundary - never approve for the user. tools/{id}/execute: a human approves in Studio, then re-issue.
A paused agent reply: re-send with approved:true plus its action_token (both). A run reading needs_approval: POST /v1/executions/{id}/continue, never the original call.Where next
- Writing the server-side proxy that calls
https://api.plungeai.com: plungeai-api-setup. - Operating the
plungeai_*tools in chat: plungeai-mcp-setup. - Picking MCP vs the One API for a given job: choose-your-plungeai-door.
- An unlisted but MCP-capable client, or the shared connect concepts (native remote vs. stdio bridge, key hygiene): plungeai-mcp-setup.
- Full guide: https://mcp.plungeai.com/docs#3-connecting-clients (opens in a new tab)