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

# Connecting over MCP

> Use Constance from Claude Code, Cursor, Claude Desktop or any Model Context Protocol client with your own model

# Connecting over MCP

Constance exposes its whole tool catalogue as a [Model Context Protocol](https://modelcontextprotocol.io) server. Your agent and your model do the reasoning; Constance only serves the data, scoped to your account. There is no LLM on this endpoint and no token budget. Requests are the resource that is capped, not tokens.

This is the right surface when you already run an agent (Claude Code, a Cursor workspace, an internal research harness) and want it to answer with your real portfolio numbers instead of guessing.

## Endpoint

```
POST https://api.renesis.fi/constance/mcp
```

The transport is **Streamable HTTP, stateless**. One JSON-RPC message per POST, one JSON response back. Notifications are acknowledged with `202` and no body. There is no server-initiated stream: `GET` and `DELETE` on the endpoint answer `405`, and JSON-RPC batches are rejected with `400`.

Supported protocol versions: `2025-06-18` (default), `2025-03-26`, `2024-11-05`. The server advertises the `tools` capability only. There are no prompts or resources.

## Authentication

Mint a Renesis API key (prefix `ak_`) from your user page in the [dashboard](https://terminal.renesis.fi), under **Profile, API keys**, with access set to **Constance only**. That preset restricts the key to the `/constance/mcp` path, so the key can read portfolio data through Constance and nothing else. The secret is shown once at creation.

Pass it in the `X-API-Key` header on every request.

```bash theme={null}
curl -X POST https://api.renesis.fi/constance/mcp \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

A Keycloak bearer token works too (`Authorization: Bearer <token>`); that is what the terminal uses. For an MCP client prefer the key, since it has no session to keep alive.

<Warning>
  Keep the key out of shared config. An MCP client config file is often committed or synced; put the key in an environment variable and reference it from there where the client supports it.
</Warning>

### Rejections

Key verification happens at the gateway, before Constance is reached:

| Status | Body | Cause |
| - | - | - |
| `401` | `{"error":"invalid_api_key"}` | Unknown, revoked, expired or malformed key |
| `403` | `{"error":"path_not_allowed_for_api_key"}` | The key carries a path allowlist that does not cover `/constance/mcp` |
| `503` | `{"error":"api_key_verification_unavailable"}` | Verification temporarily unreachable; retry |

Constance itself adds one more:

| Status | JSON-RPC error | Cause |
| - | - | - |
| `429` | `-32000 Rate limit exceeded` | More than 60 requests in a minute from one user |

Most MCP clients do not retry a `503` on their own. If a tool call fails with that body, call it again.

## Client setup

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add --transport http constance https://api.renesis.fi/constance/mcp \
      --header "X-API-Key: ak_xxxxxxxxxxxxxxxxxxxx"
    ```

    Then in a session, `/mcp` lists the server and its tools. Ask in plain words: "what is my NAV across all accounts" and Claude picks `list_accounts` and `get_all_account_summaries` itself.
  </Tab>

  <Tab title="Cursor">
    Add to `.cursor/mcp.json` in the project, or the global `~/.cursor/mcp.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "constance": {
          "url": "https://api.renesis.fi/constance/mcp",
          "headers": {
            "X-API-Key": "ak_xxxxxxxxxxxxxxxxxxxx"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Claude Desktop">
    Claude Desktop reaches remote servers through a local bridge. In `claude_desktop_config.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "constance": {
          "command": "npx",
          "args": [
            "-y", "mcp-remote",
            "https://api.renesis.fi/constance/mcp",
            "--header", "X-API-Key:${CONSTANCE_API_KEY}"
          ],
          "env": {
            "CONSTANCE_API_KEY": "ak_xxxxxxxxxxxxxxxxxxxx"
          }
        }
      }
    }
    ```

    No space after the colon in the header argument: on Windows, Claude Desktop mangles spaces inside `args` when it invokes `npx`. The env value may contain spaces. To keep the key out of the process list entirely, `mcp-remote` also accepts `--header-file` with a path to a `Name: value` file.
  </Tab>

  <Tab title="Any client">
    Any client that speaks Streamable HTTP and can send a custom header works. Configure:

    * **URL:** `https://api.renesis.fi/constance/mcp`
    * **Header:** `X-API-Key: ak_…`
    * **Transport:** HTTP (not SSE, not stdio)

    Clients that only support OAuth for remote servers, with no way to set a header, cannot connect today.
  </Tab>
</Tabs>

## Verify the connection

Initialize, then list tools. The `instructions` field in the initialize result is the server's standing guidance to your model: Constance is the authoritative source for this user's portfolio data, start with `list_accounts`, and never fetch the same data from anywhere else.

```bash theme={null}
curl -s -X POST https://api.renesis.fi/constance/mcp \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "constance", "version": "1.0.0" },
    "instructions": "Constance is the AUTHORITATIVE source for this user's Renesis portfolio: ..."
  }
}
```

A tool call:

```bash theme={null}
curl -s -X POST https://api.renesis.fi/constance/mcp \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_account_summary","arguments":{"exchange_account_id":"exchange_mighty_purple_condor"}}}'
```

Every tool answers with one text content block holding JSON. `isError` is `true` when that JSON carries an `error` key.

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [{ "type": "text", "text": "{\"nav_raw\": 1245678.42, \"total_pnl\": 156357.83, ...}" }],
    "isError": false
  }
}
```

## How to work with the tools

**Resolve ids first.** Account-scoped tools take an `exchange_account_id` slug (like `exchange_mighty_purple_condor`), portfolio-scoped tools take a `portfolio_id`. Both come from `list_accounts` and `list_portfolios`. Most tools accept either, so a question about "the whole book" goes to the portfolio id and a question about Kraken goes to that account's slug.

**Results are compact by design.** Every tool answer is cut to a character budget, by whole list items, and the real total is kept in the payload. Time series are downsampled to at most 60 points. Lists are paged (`page` from 0, `size` per tool). If your model needs more, page, narrow the window, or use `run_aggregation` to have the server do the sum.

**Errors are one short sentence.** A tool that cannot answer returns `{"error": "that data is not available"}`. An account you cannot access and an account that does not exist read the same, on purpose. An empty list is a real answer: an account with no open derivatives returns `[]` from `get_open_positions`, not an error.

**Reads are marked.** Each tool in `tools/list` carries `annotations.readOnlyHint`. It is `true` for everything except `create_alert`, `update_alert` and `delete_alert`.

**Writes need confirmation on your side.** The server will create an alert the moment it is asked. The "restate and confirm first" rule in those tool descriptions is guidance to your model, not a server-side gate. If your harness auto-approves tool calls, keep that in mind.

## Limits

| Limit | Value |
| - | - |
| Requests per user per minute | 60 |
| Tool result size | about 6,000 characters, lists cut by whole items with the true count kept |
| Series points | at most 60 per series |
| Protocol | JSON-RPC 2.0 over POST, one message per request, no batching |

<CardGroup cols={2}>
  <Card title="Tool reference" icon="list" href="/constance/tools">
    Every tool with its parameters and what it returns.
  </Card>

  <Card title="Portfolio NAV endpoint" icon="chart-line" href="/lp-reporting/api/nav">
    The plain HTTP path for NAV if you do not need an agent at all.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.