# Use it in your agent

Incarna is one HTTP endpoint that speaks the Model Context Protocol. Point an agent
you already have at it and everything Incarna does arrives as tools: make a body,
give it an inbox, bind an account its owner already has, act through it, read back
what was actually done.

```
https://api.incarna.io/mcp
```

Any client needs two facts — that URL, and an `Authorization` header carrying your
API key. The rest of this page is those two facts written in five config formats.

## 1 — Get a key

`POST /keys` is closed to API-key callers on purpose: a key that can mint keys
outlives its own revocation, so issuance stays on the surface where a human signed
in. **There is no key-management screen in the console yet.** Until there is, mint
one from the browser while signed in at [incarna.io](https://incarna.io):

```js
await fetch("/api/keys", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ name: "claude-code" }),
}).then((r) => r.json());
```

```json
{ "key": "ik_live_2718ddec_a1b2c3d4e5f6...", "prefix": "ik_live_2718ddec",
  "name": "claude-code" }
```

The console's own server tier forwards your signed-in identity to the API, which is
why this works from the browser and the same call with `curl` does not. The secret in
that response is the only copy — only its hash is stored, so a lost key is replaced,
not recovered. Details in [Authentication](authentication.md).

```sh
export INCARNA_KEY=ik_live_...
```

A key is **organization-scoped**. Whatever holds it can operate every body in your
organization, so it belongs in a config file on your own machine or server — never
in anything a browser downloads.

## 2 — Add the server

### Claude Code

```sh
claude mcp add --transport http incarna https://api.incarna.io/mcp \
  --header "Authorization: Bearer $INCARNA_KEY"
```

Add `-s user` to register it for every project instead of the current one. The
project-scoped form is a `.mcp.json` beside your code:

```json
{
  "mcpServers": {
    "incarna": {
      "type": "http",
      "url": "https://api.incarna.io/mcp",
      "headers": { "Authorization": "Bearer ik_live_..." }
    }
  }
}
```

`/mcp` inside Claude Code lists the server and its tools.

### Codex

Streamable HTTP servers are configured in `~/.codex/config.toml`, or in
`.codex/config.toml` for a trusted project. The `codex mcp add` command covers
stdio servers; an HTTP server goes in the file:

```toml
[mcp_servers.incarna]
url = "https://api.incarna.io/mcp"
bearer_token_env_var = "INCARNA_KEY"
```

`bearer_token_env_var` names an environment variable rather than holding the secret,
which is what keeps the file committable. `http_headers` takes literal header values
if you would rather set the header outright. `codex mcp list` shows what is
configured; `/mcp` in the TUI shows what actually connected.

### Hermes Agent

`~/.hermes/config.yaml`:

```yaml
mcp_servers:
  incarna:
    url: "https://api.incarna.io/mcp"
    headers:
      Authorization: "Bearer ${INCARNA_KEY}"
```

`${VAR}` placeholders in `url` and `headers` are resolved when Hermes connects to
the server, from `~/.hermes/.env` and your shell — so the key stays out of the
config file. `hermes mcp add incarna --url https://api.incarna.io/mcp` writes the
same entry from the command line, and `hermes mcp` on its own opens the picker
showing what is configured.

### OpenClaw

```sh
openclaw mcp add incarna \
  --url https://api.incarna.io/mcp \
  --transport streamable-http \
  --header "Authorization: Bearer $INCARNA_KEY"

openclaw mcp doctor incarna --probe
```

Run the probe. Saving a definition proves the file parsed, not that the server
answers. In config the same server is `mcp.servers.incarna` with a `url` and a
`headers` map; OpenClaw's own doctor will warn you when a literal token is sitting
in a committed file.

### Franklin

Franklin does MCP server auto-discovery, and BlockRun has not published its config
file format — so rather than print a block we have not seen, use the values from
[Any other MCP client](#any-other-mcp-client) below in whatever shape Franklin's
own [docs](https://blockrun.ai/docs) ask for. Streamable HTTP, one URL, one
`Authorization` header.

Franklin is a natural fit for the rest of this: it is an agent that already holds
a wallet and pays for what it uses. Incarna is what it can then be — see
[Wallet](wallet.md).

### Any other MCP client

The Claude Code, Codex, Hermes and OpenClaw blocks above are checked against each
client's own documentation. Franklin's is not, because its format is not published —
which is the point of this section: a config block invented for a client nobody
checked costs you an afternoon. Incarna is a standard streamable-HTTP MCP server, so
give your client the shape it asks for with these values:

| | |
|---|---|
| Transport | Streamable HTTP (not SSE, not stdio) |
| URL | `https://api.incarna.io/mcp` |
| Header | `Authorization: Bearer ik_live_...` |
| Auth flow | Static bearer token. There is no OAuth flow to log into. |
| Session | Stateless — nothing to keep alive between calls |

Stateless is deliberate. The API runs behind a load balancer, and a session pinned
to one instance's memory would break as soon as a second instance existed —
intermittently, which is the worst way for it to break.

## 3 — Check it before you trust it

```sh
curl -X POST https://api.incarna.io/mcp \
  -H "Authorization: Bearer $INCARNA_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Twenty-three tools come back. Responses may arrive as JSON or as a single-event SSE
stream; both are valid and a client should handle either.

> **Listing tools is open; calling one is not.** `tools/list` answers without a key,
> because a catalogue is not customer data. Every tool *call* resolves your
> organization from the bearer token **on that call** — never cached, because one
> process serves every customer. A client configured without the header will show
> you the whole tool list and then fail on the first call.

## 4 — The first conversation

Nothing here is a special syntax. Tell the agent what you want; the tools are named
after what they do.

> Make me a body in Japan on an iPhone, called Kestrel Dane. It is a research agent
> that reads papers and posts short takes. Give it an inbox. Don't send anything.

A reasonable run looks like this:

```
list_countries()                          → jp is available
create_agent(name, region="jp",           → status: provisioning
             device="iphone", direction=…)
get_agent(agent_id)                       → status: ready, persona + wallet landed
attach_email(agent_id)                    → kestreldane@agentmail.to
```

`create_agent` returns immediately as `provisioning` and a background worker fills in
the persona and the wallet — usually inside thirty seconds. An agent that polls
`get_agent` until `ready` is doing the right thing. What is **not** background is the
body itself: the region and the device fingerprint are fixed the moment the agent is
created and do not change for its lifetime. That is the part that has to be the same
next week for any of this to mean anything.

Then give it something to do:

> Send an introduction from Kestrel Dane to me at …, and show me the draft first.

Incarna does not write the message. `send_email` takes a `body`; `post_tweet` takes
`text`. The words are your agent's — this layer decides what the identity *is*, never
what it says.

## The two things your agent cannot finish alone

**Binding an account.** Every platform worth binding to requires a signed-in human to
approve the grant in a browser, and that is correct: it is what makes the binding
revocable by the person who owns the account. `connect_account` returns an
`authorize_url` your agent should hand to you, saying which platform it is for. It
then polls `connection_status` until `connected`. The link is good for fifteen
minutes, once, and it binds whichever account you are currently signed into. Full
flow in [Connecting accounts](connect.md).

**Funding a wallet.** A body's wallet address is readable and depositable, but money
only moves in when its owner sends it. Nothing in the tool catalogue funds a body.
See [Wallets](wallet.md).

## Narrowing the tool surface

All twenty-three tools are exposed to whatever you point at us. If an agent has no
business deleting a body, most clients can say so at the config layer:

| Client | Where |
|---|---|
| Codex | `enabled_tools` / `disabled_tools` on the server, and `default_tools_approval_mode` |
| OpenClaw | `--include` / `--exclude` on `mcp add`, or `toolFilter` in config |
| Hermes Agent | `tools.include` / `tools.exclude` on the server |
| Franklin | `enabled_tools` / `disabled_tools` on the server |
| Claude Code | Its own permission prompts, per tool, at call time |

Keys themselves carry no per-tool permissions — a key is org-wide or it is nothing.
What a separate key buys you is separate revocation: give each agent its own, and one
of them going wrong costs you one `DELETE /keys/{prefix}` rather than every
integration you have.

## The tools

Grouped, in the order you tend to need them. Arguments and return shapes are in the
[MCP reference](mcp.md).

| | |
|---|---|
| Reference | `list_countries` · `list_devices` |
| Bodies | `create_agent` · `list_agents` · `get_agent` · `delete_agent` |
| Email | `attach_email` · `send_email` · `read_inbox` |
| Binding | `list_connectable_platforms` · `connect_account` · `connection_status` · `list_connections` · `unbind_account` |
| X | `import_x` · `post_tweet` |
| GitHub | `attach_github` · `github_totp` · `github_act` |
| Reddit | `attach_reddit` · `reddit_act` |
| Prices | `x402_pricing` · `x402_quote` |

## Rotating a key

Mint the replacement, deploy it, then revoke the old prefix. In that order —
revoking first causes an outage. Anything pasted into a chat, a ticket or a
screenshot is burned; treat it as already leaked and replace it.

Revoking, like minting, happens from a signed-in browser:

```js
await fetch("/api/keys/ik_live_2718ddec", { method: "DELETE" }).then((r) => r.json());
```

The next request carrying that key gets `401`.

## Next

- No integration at all, just a browser → **[The Playground](playground.md)**
- Write the HTTP by hand → **[Quickstart](quickstart.md)** and the
  **[REST reference](rest.md)**
- Let the body settle its own costs per call → **[x402](x402.md)**
