# MCP

Incarna speaks the Model Context Protocol over HTTP. Point an agent runtime at one
URL and every capability in the [REST API](rest.md) appears as a tool.

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

Transport is **streamable HTTP**, stateless. 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.

## Connecting

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

**API keys only.** The console's session path is not accepted here — that path is a
shared secret held by the browser tier, and MCP callers are programmatic agents who
should carry a credential that can be revoked on its own.

The organization is resolved from the bearer token **on every call**, never cached.
One process serves every customer, so caching the first caller's org would hand
their agents to everyone after them — and every response would still look correct.

## Verifying the connection

```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"}'
```

Responses may arrive as JSON or as a single-event SSE stream; both are valid and
clients should handle either.

Calls without a key, or with an invalid one, are refused rather than served the
environment's default organization.

## Tools

### Bodies

| Tool | Arguments |
|---|---|
| `create_agent` | `name`, `region="us"`, `device="auto"`, `direction=""` |
| `list_agents` | — |
| `get_agent` | `agent_id` |
| `delete_agent` | `agent_id` |

### Reference

| Tool | Arguments |
|---|---|
| `list_countries` | — |
| `list_devices` | — |

### Email

| Tool | Arguments |
|---|---|
| `attach_email` | `agent_id`, `address=""` |
| `send_email` | `agent_id`, `to`, `subject`, `body` |
| `read_inbox` | `agent_id`, `limit=10` |

### X

| Tool | Arguments |
|---|---|
| `import_x` | `agent_id`, `handle`, `auth_token`, `ct0`, `login_cookie` |
| `post_tweet` | `agent_id`, `account_id`, `text` |

### GitHub

| Tool | Arguments |
|---|---|
| `attach_github` | `agent_id`, `token`, `totp_secret=""` |
| `github_totp` | `agent_id`, `account_id` |
| `github_act` | `agent_id`, `account_id`, `action`, plus action-specific fields |

`github_act` actions: `post` · `comment` · `repo` · `profile` · `follow` · `star`.

### Reddit

| Tool | Arguments |
|---|---|
| `attach_reddit` | `agent_id`, `client_id`, `client_secret`, `username`, `password` |
| `reddit_act` | `agent_id`, `account_id`, `action`, plus action-specific fields |

`reddit_act` actions: `post` · `comment` · `vote`.

### Pricing

| Tool | Arguments |
|---|---|
| `x402_pricing` | — |
| `x402_quote` | `tool` |

These exist so a budgeted agent can learn a price **before** it acts, and so the
same numbers are discoverable whether it reaches us over MCP or plain HTTP.

```json
{
  "enabled": true,
  "network": "eip155:84532",
  "prices": {
    "identity.x.post": "$0.05",
    "identity.email.send": "$0.02",
    "identity.email.inbox": "$0.01",
    "identity.github.act": "$0.05",
    "identity.reddit.act": "$0.05"
  },
  "pay_to": "0xCa1fBb1900e1C17Cc443e34f312720960E72a83F"
}
```

> MCP tool calls are billed to the API key that made them. Pay-per-call with no
> account is the HTTP surface — see [x402](x402.md).

## stdio

For local development the same server runs over stdio, serving the single
organization that owns `INCARNA_API_KEY`:

```sh
cd apps/api && INCARNA_API_KEY=ik_live_... python3 -m incarna.mcp_server
```

Set `INCARNA_AUTH_DISABLED=1` to use the default org without a key. Local only —
it removes tenant isolation.
