IncarnaDocsGitHubConsole

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:

await fetch("/api/keys", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ name: "claude-code" }),
}).then((r) => r.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.

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

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:

{
  "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:

[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:

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

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 below in whatever shape Franklin's own 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.

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

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.

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.

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.

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:

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

The next request carrying that key gets 401.

Next