> ## 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.

# Tools — structured tool-agents and their contracts

> A tool on PlungeAI is a structured agent: instead of free-text prompts it takes typed parameters against a published contract — named operations, a JSON Schema for inputs, worked examples, and per-operation approval gates.


<!-- sources-of-truth: orchestration/api-gateway/openapi.ts, orchestration/mcp-gateway/server.ts, orchestration/mcp-gateway/tools.ts, core/core-base/tool-contract.ts, core/core-base/call-agent.ts | last-synced: 2026-09-24 (re-verified: reserved-param list against tool-contract.ts RESERVED_TASK_FIELDS, 409/403 fence behavior against call-agent.ts checkAgentCall unattended vs interactive branches, and routes/tools.ts execute handler — all match, no drift found) -->
A **tool** on PlungeAI is a structured agent: instead of free-text prompts it takes
**typed parameters** against a published contract — named operations, a JSON Schema
for inputs, worked examples, and per-operation approval gates. Think of document
converters, weather lookups, data-table CRUD, calendar operations, payment actions.

The rule that prevents almost every tool failure: **fetch the contract before the
first call to an unfamiliar tool. The contract IS the API documentation** — current,
generated from the live card, and (over MCP) personalized with the acting user's
credential status.

## The contract

`GET /v1/tools/{id}` (One API) or `plungeai_get_tool_contract {agent_id}` (MCP):

```json
{
  "agent_id": "markitdown",
  "name": "markitdown",
  "description": "Convert a document, image, audio file, or URL to markdown…",
  "operations": [
    {
      "name": "convert",
      "purpose": "Convert a file or URL to markdown",
      "gated": false,
      "required_params": ["file_data", "file_name"]
    }
  ],
  "inputSchema": { "type": "object", "properties": { "…": {} } },
  "examples": [ { "title": "Convert a PDF to markdown", "cnl": "…worked YAML…" } ],
  "output_type": "markdown"
}
```

Read it in this order:

1. **`operations`** — pick the operation whose `purpose` matches the job. Note
   `gated: true`: that operation needs human approval (see fences below).
2. **`inputSchema`** — the exact parameter shapes. `required_params` per operation is
   the short list; the schema is the authority on types and enums.
3. **`examples`** — worked CNL YAML; each example also shows how the tool is used as
   a workflow task (the same fields go on a task next to `agent:` — see
   the `plungeai-workflows` skill).
4. **Credential status** (MCP contract only) — LIVE per-user: "platform-managed"
   means it just works; "connect Google first" means the call will return
   `needs_connection` until the user connects that credential in Studio.

## Discovery

```bash
# List active tool-agents
curl -s "https://api.plungeai.com/v1/tools?limit=50" \
  -H "Authorization: Bearer ozk_YOUR_KEY"
# → {tools: [Card…], count}
```

Or find by capability with the hybrid search (`GET /v1/discovery/search?q=…`) /
`plungeai_list_agents {search}` — tools are agents; they appear in the same registry
with a Parameters table on their card.

## Execution

### MCP: `plungeai_execute_tool`

```
plungeai_execute_tool {
  agent_id: "markitdown",
  operation: "convert",
  params: { file_data: "<base64 of the file>", file_name: "report.pdf" }
}
```

Runs through the engine with full observability (an execution id you can trace).
Long jobs: `mode: "async"` → poll `plungeai_get_workflow_status` → fetch with
`plungeai_get_result`.

Every call answers with a **structured outcome envelope over HTTP 200** — a blocked
call is guidance, never a protocol error. The complete MCP status vocabulary:

| Outcome | Meaning | Your move |
|---|---|---|
| `ok` | Result attached | Relay it verbatim |
| `needs_input` | Missing/invalid fields; the outcome carries them + the schema | Fix exactly those fields; retry once |
| `needs_connection` / `needs_api_key` | User credential missing | Tell the user exactly what to connect in the platform apps; retry after |
| `needs_approval` | A gated/money operation paused the run (`⏸`) | Relay the approval block verbatim; only after the user explicitly approves, `plungeai_continue {approve: true}`; their "no"/changes go in `message` |
| `unavailable` | Tool/dependency not live (or fenced off); alternatives listed | Pick an alternative or re-discover |
| `error` | Downstream execution failed; the summary names the cause | Read it; inspect the trace (`plungeai-results-traces`); do not hammer-retry |

There is no `refused` MCP status: MCP has an approval surface, so gated operations
pause as `needs_approval` instead of being refused. `refused` is the One API's REST
spelling of the fence (`403` below).

### One API: `POST /v1/tools/{id}/execute`

```bash
curl -s -X POST https://api.plungeai.com/v1/tools/markitdown/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "operation": "convert",
    "params": { "file_data": "<base64 of the file>", "file_name": "report.pdf" }
  }'
# 200 → {"ok": true, "content": "…", "outcome": "ok", "request_id": "…"}
```

`prompt` is accepted instead of `operation`/`params` for prompt-driven agents reached
through this route — but if the card has a Parameters table, use typed params.

HTTP status mapping (same fences, REST spelling):

| Status | Code | Meaning |
|---|---|---|
| `403` | `refused` | Fence — gated/money operation refused unattended. Do not retry. |
| `404` | `unknown_tool` | Id not in the live catalog — re-discover |
| `409` | `approval_required` | The dispatched run's own outcome paused for approval (`needs_approval`) — real and reachable on this route. Approve out-of-band (Studio), then re-issue the identical request; the One API has no continuation token like MCP's `plungeai_continue`. |
| `422` | `invalid_params` | Body carries the missing/reserved fields AND the contract — fix exactly those |
| `502` | `execution_failed` | Downstream failure — inspect the trace (`plungeai-results-traces`) |

## Why the fences exist

Gated operations are the ones with real-world blast radius: money movement, outbound
messages, irreversible mutations. The platform's trust model is that **an unattended
caller never fires them** — a human must be in the loop. `403`/`409` are therefore
correct behavior, not errors to engineer around. Surface them, get the human
decision, continue through the approval mechanism. Also remember: a retried tool
call re-fires the FULL operation — if the side effect completed before the failure
was reported, a retry duplicates it. When in doubt, check execution history
(`plungeai_executions`) before re-firing anything non-idempotent.

## Tools inside workflows

A structured tool-agent is used in CNL by putting its typed fields directly on the
task, next to `agent:`:

```yaml
- type: task
  id: convert
  agent: markitdown
  operation: convert
  file_data: "{input}"
  file_name: report.pdf
```

The contract's `examples[].cnl` show the canonical per-tool shapes. Everything else
about composition is in the `plungeai-workflows` skill.

## Checklist

1. Contract fetched (first use) — operation picked by `purpose`, gates noted.
2. Params match `inputSchema` exactly — no extra prose fields.
3. Credential status green (MCP) or the user warned.
4. Gated op → plan for the approval pause; unattended REST cannot serve it.
5. Outcome remediation followed; no blind retries; no duplicate side effects.
