# Connect an AI agent over MCP

[Create an API key →](https://tracefast.xyz/settings/api/keys)

TraceFast runs a [Model Context Protocol](https://modelcontextprotocol.io)
server: an AI agent calls TraceFast's Ethereum data as tools. Each tool is one
method of the [REST API](/api) — the same data, the same bounds, the same
refusals — and it is called under your plan: with an API key, or by signing
in with your TraceFast account. A tool call counts against a rate limit and
your account's daily quota exactly like a REST call; listing the tools does
not count.

```
Address:     https://tracefast.xyz/mcp
Transport:   Streamable HTTP, one POST per request
Protocol:    2026-07-28; 2025-11-25 clients are served too
Credential:  a TraceFast API key, or sign-in with your TraceFast account
```

## Connect a client

There are two ways in. Claude Code, Cursor, your own agent and claude.ai
organizations send an **API key** in a request header; a personal claude.ai
account **signs in** with your TraceFast account instead — see
[below](#claudeai-on-a-personal-account).

The key goes in one request header. Use the first one; the second exists for
clients that can only send a bearer token:

| Header | When |
|---|---|
| `X-Api-Key: tf_live_…` | the client lets you set a header |
| `Authorization: Bearer tf_live_…` | the client can only send a bearer token |

Keys are created in the TraceFast app on a paid plan — see
[Managing API keys](/api/keys). Every paid plan can connect; the plan decides
which tools answer (see [Tools](#tools)).

### Claude Code

```bash
claude mcp add --transport http tracefast https://tracefast.xyz/mcp \
  --header "X-Api-Key: $TRACEFAST_API_KEY"
```

The shell expands the variable, so the key is saved in your Claude Code
configuration. Add `--scope user` to have TraceFast in every project; `/mcp`
inside Claude Code shows whether it is connected.

To share the server through a repository without committing the key, put it in
the project's `.mcp.json` — Claude Code reads `${TRACEFAST_API_KEY}` from the
environment of each person who opens the project:

```json
{
  "mcpServers": {
    "tracefast": {
      "type": "http",
      "url": "https://tracefast.xyz/mcp",
      "headers": { "X-Api-Key": "${TRACEFAST_API_KEY}" }
    }
  }
}
```

### Cursor

In `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one:

```json
{
  "mcpServers": {
    "tracefast": {
      "url": "https://tracefast.xyz/mcp",
      "headers": { "X-Api-Key": "${env:TRACEFAST_API_KEY}" }
    }
  }
}
```

### claude.ai on a Team or Enterprise plan

An organization Owner adds TraceFast once, for the whole organization:

1. Open **Organization settings → Connectors** and click **Add custom connector**.
2. Enter the URL `https://tracefast.xyz/mcp`.
3. Open **Request headers**, choose `x-api-key` and paste the key.

Members then turn the connector on in their own connector settings.

* **One key for everyone.** Every member's calls go through the key the Owner
  entered: they share its rate limit and your account's daily quota, and show
  in its usage as one.
* **Request headers are a claude.ai beta** open to a limited set of
  organizations. If the dialog has no **Request headers** section, your
  organization cannot connect TraceFast with a key yet.

### claude.ai on a personal account

A personal account connects without a key, by signing in with your TraceFast
account:

1. In claude.ai open **Settings → Connectors** and click **Add custom connector**.
2. Name it `TraceFast` and enter the URL `https://tracefast.xyz/mcp`.
3. Keep what the dialog detects: authentication **Sign in now**, OAuth client
   **Register automatically**. Leave **Request headers** empty — a key there
   turns every call into a call with that key.
4. Click **Connect**, sign in to TraceFast, and read the consent screen: it
   names the access asked for (`tracefast:read`), your plan and the address it
   returns to. Click **Allow**.

The connector stays connected and renews its sign-in on its own. The dialog
also offers your own OAuth client or Claude's published identity; neither is
needed.

* **Your plan decides the tools.** A tool above your plan answers with a
  [plan refusal](#the-plan).
* **One budget for every agent you sign in.** They share one rate limit and one
  concurrency cap, and their calls count toward your account's daily quota,
  which your API keys share too.
* **To disconnect**, remove the app under
  [Settings → Connected apps](https://tracefast.xyz/settings/apps) in TraceFast.

A client that connects with neither a key nor a sign-in gets `401` with a
`WWW-Authenticate` header pointing at TraceFast sign-in (`resource_metadata`,
scope `tracefast:read`). A client that supports MCP sign-in starts it; one that
does not reports an authentication error — give it the key header.

### Your own agent

Any MCP client with the Streamable HTTP transport that can set a request header
works. MCP SDKs negotiate the protocol version and set the protocol headers
themselves.

Call the server from a backend or a local process. A request that carries a
browser `Origin` header is refused, and the endpoint answers no CORS preflight
— it cannot be called from a web page.

## Calling it by hand

For raw HTTP — `curl`, a test — protocol `2026-07-28` needs no handshake and
no session, but every request must carry:

* the header `MCP-Protocol-Version: 2026-07-28`, matching the body;
* the header `Mcp-Method` with the JSON-RPC method, and on `tools/call` also
  `Mcp-Name` with the tool name;
* `params._meta` with **both** `io.modelcontextprotocol/protocolVersion` and
  `io.modelcontextprotocol/clientCapabilities`. An empty object is a valid
  `clientCapabilities`.

A request without one of the `_meta` keys is refused with HTTP `400` and
JSON-RPC error `-32602`; `error.data.missing` lists the keys that were absent.
SDK clients add them on their own — this matters only when you write the body
yourself.

List the tools:

```bash
curl -s https://tracefast.xyz/mcp \
  -H "X-Api-Key: $TRACEFAST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/list",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'
```

The list is one page, the same for every account, and carries `ttlMs: 3600000`
with `cacheScope: public`: a client may keep it for an hour. It is large — each
tool comes with its full input and output schemas. Protocol calls such as
`server/discover` and `tools/list` do not count toward your plan's rate or daily
quota; only tool calls do.

Call a tool:

```bash
curl -s https://tracefast.xyz/mcp \
  -H "X-Api-Key: $TRACEFAST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/call" \
  -H "Mcp-Name: get_latest_block" \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "tools/call",
    "params": {
      "name": "get_latest_block",
      "arguments": {},
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'
```

## Results

A tool that answers returns its REST response whole in `structuredContent`,
and the same JSON as one text block for clients that read only text:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "structuredContent": {
      "data": { "number": 26091151, "hash": "0xfdd109390c63bb9efa2d86af53b7b06769d7c2151d9a7f95b69e2f076a5b12f7", "…": "…" },
      "meta": {
        "chain": "ethereum",
        "timestamp": 1790781616092,
        "cached": false,
        "asOfBlock": 26091151,
        "docs": "https://docs.tracefast.xyz/api/block-latest"
      },
      "links": { "web": "https://tracefast.xyz/blocks/26091151" }
    },
    "content": [{ "type": "text", "text": "{\"data\":{…},\"meta\":{…},\"links\":{…}}" }],
    "isError": false
  }
}
```

* **`data` and `meta`** are the method's REST answer — see
  [Conventions](/api/conventions#responses). `meta.asOfBlock` is the block the
  answer is valid at, `meta.cached` says whether it came from the response
  cache, `meta.docs` links the method page. Not every source reaches the chain
  head at the same moment: see [Data freshness](/api/conventions#data-freshness).
* **`links.web`** is the TraceFast page of what the answer is about — the
  entity page of an address or token, the page of a transaction or a block —
  so an agent can hand its user a link. `resolve_entity` links its best match,
  and carries no `links` when nothing matched.
* **List tools return a small page by default** — smaller than the REST method's
  — and take `limit` up to the maximum their description states. A larger
  `limit` is refused, never cut. Tools with a `cursor` argument continue with
  the `nextCursor` inside `data`; the others answer in one call.

## Refusals

A call can be refused at four points, and each looks different:

| Refused by | What the agent receives | For example |
|---|---|---|
| the credential or a limit | HTTP `401` or `429` with the REST refusal body — no tool runs | `api_key_required`, `invalid_api_key`, a `429` with `bucket` |
| the plan | HTTP `200`: a tool result with `isError: true` and `error.code` `forbidden` | `plan_scope_missing` |
| the protocol | a JSON-RPC `error` with an integer code | a missing `_meta` key, an unknown tool or argument |
| the data | HTTP `200`: a tool result with `isError: true` | `not_found`, `block_not_available`, `bad_request` |

### The key and the limits

These come from the credential check, before any tool runs, in the same
envelope as on REST. With no credential the `401` also carries a
`WWW-Authenticate` header that points at TraceFast sign-in:

```json
{
  "error": {
    "code": "auth_failed",
    "message": "authentication failed: sign in with your TraceFast account (OAuth) or send an API key",
    "reason": "api_key_required"
  }
}
```

* `401` `api_key_required` — neither a key nor a valid sign-in was sent: no
  credential, a header value that is not a TraceFast key, or a sign-in that
  is invalid or expired. Sign in again, or send the key.
* `401` `invalid_api_key` — the value is not a valid key. A disabled, expired or
  revoked key has its own `reason`.
* `429` — a rate limit or the daily quota; `bucket` names which one, and
  `Retry-After` says when to try again where waiting helps. Tool calls share
  the key's request rate with its REST calls: on a plan with a low rate, two
  calls sent back to back can get `429` with `bucket: base`. Pace them.

Every `reason` and `bucket` is explained on
[API keys](/api/authentication#when-a-key-is-refused), with the limits under
[rate limits and quotas](/api/authentication#rate-limits-and-quotas).

### The plan

A tool your plan or your key does not open is not an HTTP error on MCP — REST
answers `403` for the same thing. It is a tool result on HTTP `200` with
`isError: true`, so the agent can read why and tell its user:

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "resultType": "complete",
    "structuredContent": {
      "error": {
        "code": "forbidden",
        "message": "forbidden: your plan does not include this method — see https://tracefast.xyz/pricing for the plans that do",
        "reason": "plan_scope_missing",
        "details": { "upgrade": "https://tracefast.xyz/pricing" },
        "requestId": "…"
      }
    },
    "content": [{ "type": "text", "text": "{\"error\":{…}}" }],
    "isError": true
  }
}
```

| `reason` | What happened | What to do |
|---|---|---|
| `plan_scope_missing` | the tool needs a higher plan than yours; `details.upgrade` links the plans | upgrade the plan — the key itself is fine |
| `key_scope_missing` | your plan opens the tool, but the key was issued without it | issue or rotate a key that includes it |
| `endpoint_group_not_allowed` | this key may not call this tool | check the plan and the key's permissions |
| `account_suspended` | the account is suspended | contact support |

`tools/list` is the same for every account, so an agent sees tools its plan
does not open; the plan each tool needs is in [Tools](#tools).

### The protocol

A request the server cannot take as MCP gets a JSON-RPC `error` with an integer
code, on HTTP `400` in protocol `2026-07-28`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "request _meta is missing: io.modelcontextprotocol/clientCapabilities",
    "data": {
      "missing": ["io.modelcontextprotocol/clientCapabilities"],
      "requestId": "01a0f2e6-f020-7e20-b144-c1ebe68220f4"
    }
  }
}
```

| `code` | Meaning |
|---|---|
| `-32602` | invalid params: an unknown tool; an argument the tool does not take, a missing required argument or a value of the wrong type — `error.data.argument` names it; a missing `_meta` key — `error.data.missing` lists them |
| `-32020` | `Mcp-Method`, `Mcp-Name` or `MCP-Protocol-Version` disagrees with the body, is repeated, or is missing |
| `-32022` | the protocol version is not served; `error.data.supported` lists the ones that are |
| `-32601` | the method does not exist — the server offers tools only, no resources, prompts or subscriptions |
| `-32600` | not a single JSON-RPC 2.0 request — a batch is refused; also a body over 16 KiB, an HTTP method other than `POST` and a browser `Origin` |
| `-32700` | the body is not JSON |

`error.data.requestId` identifies the request — quote it when you contact
support. A client on protocol `2025-11-25` receives the same errors on HTTP
`200`, as that version expects.

### The data

A tool the data refuses — a malformed address, a block the method cannot
answer yet, a `limit` above the tool's maximum — is not an HTTP error. It is an
ordinary result on HTTP `200` with `isError: true`, and `structuredContent`
holds the refusal the REST method gives:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resultType": "complete",
    "structuredContent": {
      "error": {
        "code": "not_found",
        "message": "no block at height 99999999",
        "requestId": "01a0f2e6-efec-73f2-9992-615a0b686e86"
      }
    },
    "content": [{ "type": "text", "text": "{\"error\":{…}}" }],
    "isError": true
  }
}
```

Branch on `error.code` and `details.reason` exactly as on REST — the registry
is under [Errors](/api/conventions#errors). A `block_not_available` means "not
built yet" or "not visible right now", never "does not exist": see
[Data freshness](/api/conventions#data-freshness).

## Tools

The list below is generated from the server's own `tools/list`. Each tool is one
method of the REST API: the same arguments under the same names, the same data
and the same refusals — its method page is the reference. The heading is the
lowest plan that opens the tool; higher plans include it
([plans](/api/authentication#plans)).

### Enriched Data

| Tool | What it answers |
|---|---|
| `get_block` | [One block](/api/block) |
| `get_block_authorizations` | [EIP-7702 authorizations of one block](/api/block-authorizations) |
| `get_block_transactions` | [Transactions of one block](/api/block-transactions) |
| `get_block_withdrawals` | [Validator withdrawals of one block](/api/block-withdrawals) |
| `get_contract` | [One contract](/api/contract) |
| `get_contract_bytecode` | [Bytecode of a contract](/api/contract-bytecode) |
| `get_contract_proxy` | [Implementations of a proxy contract](/api/contract-proxy) |
| `get_contract_selectors` | [Function selectors of a contract](/api/contract-selectors) |
| `get_entity_activity` | [Activity of an address](/api/address-activity) |
| `get_entity_balance_history` | [Native balance history of an address](/api/address-balance-history) |
| `get_entity_daily_activity` | [Daily activity of an address](/api/address-daily-activity) |
| `get_entity_overview` | [One address](/api/address) |
| `get_latest_block` | [The latest block](/api/block-latest) |
| `get_token` | [One token contract](/api/token) |
| `get_token_stats` | [Token statistics of the network](/api/token-stats) |
| `get_token_transfers` | [Transfers of a token](/api/token-transfers) |
| `get_transaction` | [One transaction](/api/transaction) |
| `get_transaction_events` | [Event logs of a transaction](/api/transaction-events) |
| `get_transaction_failures` | [Why a transaction reverted](/api/transaction-failures) |

### Intelligence

| Tool | What it answers |
|---|---|
| `get_block_addresses` | [Accounts touched by one block](/api/block-addresses) |
| `get_block_proposers` | [Block-proposal leaderboard](/api/consensus-proposers) |
| `get_entity_attribute_history` | [Attribute history of an address](/api/address-attribute-history) |
| `get_entity_attribute_proof` | [Proof of one attribute of an address](/api/address-attribute-proof) |
| `get_entity_attributes` | [Attributes of an address](/api/address-attributes) |
| `get_entity_attributes_at_block` | [Attributes of an address at a block](/api/address-attributes-at-block) |
| `get_entity_counterparties` | [Address counterparties](/api/address-counterparties) |
| `get_entity_portfolio` | [Portfolio of an address](/api/address-portfolio) |
| `get_entity_relation_history` | [Relation history of an address](/api/address-relation-history) |
| `get_entity_relation_proof` | [Proof of one relation of an address](/api/address-relation-proof) |
| `get_entity_relations` | [Relations of an address](/api/address-relations) |
| `get_entity_relations_at_block` | [Relations of an address at a block](/api/address-relations-at-block) |
| `get_entity_token_balance` | [Balance of one token on an address](/api/address-token-balance) |
| `get_token_holders` | [Top holders of a token](/api/token-holders) |
| `get_validator_queue_history` | [Validator queues over time](/api/consensus-queues) |
| `get_validator_queues` | [Validator queues now](/api/consensus-current) |
| `get_validator_withdrawals` | [Withdrawals of a validator](/api/validator-withdrawals) |
| `resolve_entity` | [Resolve a name, symbol or address](/api/entity-resolve) |

### Investigation

| Tool | What it answers |
|---|---|
| `get_blacklist_events` | [Blacklist events about one address](/api/risk-blacklist-events) |
| `get_blacklist_status` | [Which issuer contracts list one address](/api/risk-blacklist-status) |
| `get_contract_events` | [Events emitted by a contract](/api/contract-events) |
| `get_entity_internal_transfers` | [Internal transfers of an address](/api/entity-internal-transfers) |
| `get_entity_token_transfers` | [Token transfers of an address](/api/entity-token-transfers) |
| `get_entity_transactions` | [Transactions of an address](/api/entity-transactions) |
| `get_trace_frames` | [Call frames of a transaction, paged](/api/trace-frames) |
| `get_transaction_storage_writes` | [Storage writes of a transaction](/api/transaction-storage-writes) |
| `get_transaction_trace` | [Execution trace of a transaction](/api/trace) |

## Related pages

* [API keys](/api/authentication) — plans, refusals and limits
* [Managing API keys](/api/keys) — creating, rotating and revoking keys
* [Conventions](/api/conventions) — responses, errors and data freshness
* [All methods](/api/methods) — every REST method on one page
* [Paying with x402](/api/x402) — the key-less way, paid per call
