> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doomberg.me/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Connection

> How agents connect to Ithaca over MCP — stdio vs streamable-HTTP transports, bearer-token authentication, the initialize handshake, tool discovery, resource URIs, and the session lifecycle.

Ithaca exposes its tools over the Model Context Protocol (MCP). Agents connect using one of two transports — **stdio** for local, trusted execution or **streamable-HTTP** for remote, hosted connections. Every remote connection is authenticated with a per-tenant bearer token and scoped to the caller's capabilities.

## Transports

<Tabs>
  <Tab title="stdio (local)">
    The default transport for local development. The MCP server runs as a child process of the agent, communicating over stdin/stdout. No network, no auth — the trust boundary is the local machine.

    ```bash theme={null}
    # run the MCP server in stdio mode (the default)
    export DOOMBERG_DB=./doomberg.db
    uv run python -m doomberg_mcp
    ```

    The agent launches this process and speaks MCP over its stdio pipes. This is the path used when you self-host or run the demo end-to-end script.

    <Callout type="warn">
      stdio mode has **no authentication**. It is intended for local, trusted use only. Never expose a stdio MCP server over the network.
    </Callout>
  </Tab>

  <Tab title="streamable-HTTP (remote)">
    The hosted transport for bring-your-own agents connecting over the network. The MCP server runs as a Starlette ASGI app (via `FastMCP.streamable_http_app()`) served by uvicorn, with a pure-ASGI auth middleware enforcing per-tenant bearer tokens and scopes.

    ```bash theme={null}
    # run the MCP server in HTTP mode
    export DOOMBERG_MCP_TRANSPORT=http
    export DOOMBERG_MCP_PORT=8080
    export DOOMBERG_DB=postgresql://user:pass@host:5432/db
    uv run python -m doomberg_mcp
    ```

    The default hosted endpoint is `https://mcp.doomberg.me/mcp`. The installer registers this URL with your agent.

    Every HTTP MCP request must carry:

    ```http theme={null}
    Authorization: Bearer dmbg_<env>_<keyid>_<secret>
    ```

    Missing, invalid, or revoked keys return `401` with `WWW-Authenticate: Bearer realm="doomberg-mcp"`.
  </Tab>
</Tabs>

## Authentication

Remote connections authenticate with a per-tenant API key in the `Authorization: Bearer` header. The key format is frozen by the auth contract:

```
dmbg_<env>_<keyid>_<secret>
```

| Segment    | Meaning                                                                                   |
| ---------- | ----------------------------------------------------------------------------------------- |
| `dmbg`     | Fixed prefix (lets leaked keys be scanned and revoked)                                    |
| `<env>`    | Deployment env tag (`test` by default, `prod` in production)                              |
| `<keyid>`  | Public, stable credential id (UUID4 hex). Used for lookup and audit attribution           |
| `<secret>` | High-entropy URL-safe token. **Never stored** — only `sha256(salt + secret)` is persisted |

### Verification flow

The `TenantAuthMiddleware` resolves every request:

1. Extract the bearer token from the `Authorization` header.
2. Split into `(prefix, env, key_id, secret)` — the secret may contain `_`, so only the first three separators are split.
3. Look up the `key_id` row in the `api_keys` table.
4. If the key is revoked (`revoked = 1`), return `None` → 401.
5. Compute `sha256(salt + secret)` and compare with the stored hash using `hmac.compare_digest` (constant-time).
6. On match, return `{tenant_id, scopes, key_id}` → the `Principal`.

The principal is stashed in a `contextvars.ContextVar` so the tool wrappers can pass it into the `Toolset` as `_ctx`. Every hosted call is then tenant-scoped and traced under that tenant.

### Scope enforcement

Scopes are enforced per-request by inspecting the JSON-RPC body:

| Scope              | Required for                                             |
| ------------------ | -------------------------------------------------------- |
| `data:read`        | Any `tools/call` or resource read/list request           |
| `strategy:author`  | The `propose_strategy` tool                              |
| `runs:write`       | The `run_skill` tool                                     |
| `runs:cancel`      | The `cancel_run` tool                                    |
| `approvals:read`   | The `list_approvals` tool                                |
| `approvals:decide` | The `decide_approval` tool (also requires a human actor) |
| `artifacts:read`   | The `get_artifact` tool                                  |

Missing scopes return `403 missing scope: <scope>`. Batch requests get the union of all required scopes.

## The MCP initialize handshake

When the agent connects, the MCP client sends an `initialize` request. The Ithaca server responds with its capabilities and instructions:

```json theme={null}
// client → server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": { ... },
    "clientInfo": {"name": "codex", "version": "1.0.0"}
  }
}

// server → client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {"listChanged": false},
      "resources": {"listChanged": false, "subscribe": false}
    },
    "serverInfo": {"name": "doomberg", "version": "0.0.0"},
    "instructions": "You are connected to Ithaca..."
  }
}
```

The `instructions` field carries the canonical workflow (see [Agent Workflow](/agent/workflow#server-instructions)). The agent is expected to read and follow it.

After `initialize`, the client sends `notifications/initialized` and the session is ready for `tools/list`, `tools/call`, `resources/list`, and `resources/read`.

## Tool discovery

Ithaca exposes 60+ tools. Loading all of them into the agent's context at once is wasteful and can overflow the context window. Ithaca supports **progressive discovery**:

### search\_skills — progressive discovery

```json theme={null}
// tool call
search_skills(query="volatility", asset_class="market", intent="risk")

// expected output (abbreviated)
{
  "skills": [
    {
      "skill_id": "vol.realized",
      "name": "Realized Volatility",
      "description": "Annualized realized (close-to-close log-return) volatility...",
      "runtime_profile": "interactive",
      "asset_class": "vol",
      "source_classes": ["computed"],
      "trigger_compatible": false
    },
    {
      "skill_id": "vol.term_structure",
      "name": "Vol Term Structure",
      ...
    }
  ],
  "count": 6,
  "truncated": false
}
```

The agent searches for skills matching a query, asset class, or intent, then calls `describe_skill` for the ones it wants to use. This keeps the context window small.

### describe\_skill — one skill's manifest

```json theme={null}
// tool call
describe_skill(skill_id="vol.realized")

// expected output (abbreviated)
{
  "skill_id": "vol.realized",
  "version": "1.0",
  "name": "Realized Volatility",
  "description": "Annualized realized (close-to-close log-return) volatility...",
  "input_schema": {...},
  "output_schema": {...},
  "runtime": {"profile": "interactive"},
  "source_classes": ["computed"]
}
```

### tools/list — list all tools

The standard MCP `tools/list` method returns every registered tool. This is the full catalog — use it when you need the complete list, but prefer `search_skills` for progressive discovery in a research session.

## Resource URIs

Ithaca exposes read-only resources under the `doomberg://` scheme. These are tenant-scoped (HTTP mode requires `data:read`) and return credential-redacted JSON.

| URI                                      | Description                                                                                   |
| ---------------------------------------- | --------------------------------------------------------------------------------------------- |
| `doomberg://skills/catalog`              | Compact skill catalog for progressive discovery                                               |
| `doomberg://skills/{skill_id}/{version}` | One versioned skill manifest                                                                  |
| `doomberg://runs/{run_id}`               | Durable run with jobs, artifacts, and replayable trace events                                 |
| `doomberg://policies/current`            | Current deterministic platform policy (trading disabled, execution limits, typed error codes) |
| `doomberg://accounts`                    | Tenant-scoped account and provider connection summary (credentials redacted)                  |
| `doomberg://data/providers`              | Tenant-scoped data-provider and dataset snapshot summary                                      |

### Example: read the policies resource

```json theme={null}
// resources/read
{"uri": "doomberg://policies/current"}

// expected output
{
  "kind": "policies.current",
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
  "trading": {"enabled": false, "detail": "data and backtest only"},
  "paper_trading_default": false,
  "live_execution": {"enabled": false, "requires_connected_provider": false},
  "execution": {
    "max_notional_usd": 10000,
    "max_quantity": 100,
    "max_freshness_ms": 60000
  },
  "typed_errors": ["AUTH_REQUIRED", "BROKER_CONNECTION_REQUIRED", ...]
}
```

### Credential redaction

Resource responses never expose credential material. Provider connections return `credential_configured: true/false` but never `credential_ref`. IBKR-sourced data is sanitized: `source_account`, `account_ref`, `host`, `quote_path`, and `order_path` are stripped; the provider name is replaced with the public data-source label.

## Session lifecycle

A Ithaca MCP session maps to a persistent **research run** in the platform store. The lifecycle:

```
connect (initialize)
  → session_context (opens/reuses a research run, emits session_open)
  → tool calls (each traced under the run_id)
  → subscribe_run / get_run (poll progress)
  → disconnect (session ends; run persists for replay)
```

### Session correlation

The `Toolset` keys research sessions off `(tenant_id, agent_id)`:

* In **stdio mode**, `agent_id` defaults to a per-process UUID. All calls in one process share a research run.
* In **HTTP mode**, `agent_id` is the verified `key_id`. All calls from one credential share a research run.
* If `session_context` is called with an explicit `session_id` (a research-run UUID), subsequent calls attach to that run. This lets the web UI start a session and the agent join it.

### Trace events

Every tool call emits a `tool_call` event (with the tool name and arguments) and a `tool_result` event (with a provenance summary). These are appended to the `trace_event` table with a gap-free monotonic `seq` per `run_id`. The web UI replays them via SSE.

| Event type          | When                                                         |
| ------------------- | ------------------------------------------------------------ |
| `session_open`      | First `session_context` call in a session                    |
| `reasoning`         | `session_context` with a prompt, or agent-narrated reasoning |
| `tool_call`         | Before a tool executes                                       |
| `tool_result`       | After a tool returns                                         |
| `strategy_proposed` | `propose_strategy` succeeds                                  |
| `run_transition`    | Run state changes (draft→queued→running→succeeded)           |
| `denial`            | A money-path action is refused (agent tried to promote)      |
| `error`             | A tool or transition fails                                   |

### Disconnect and replay

When the MCP session ends, the research run persists. The human can open [app.doomberg.me](https://app.doomberg.me) → **Sessions** and replay the entire trace from `seq=0`. SSE replay uses `Last-Event-ID` to resume from any point without gaps.

## Related

<Card title="Install an Agent" icon="download" href="/quickstart">
  The one-liner installer that registers the hosted MCP endpoint with your agent.
</Card>

<Card title="Agent Workflow" icon="route" href="/agent/workflow">
  The canonical research workflow and server instructions.
</Card>

<Card title="Security Model" icon="shield-halved" href="/api/overview">
  Tenant isolation, API key security, and the glass-box principle.
</Card>
