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

# MCP connection

> Connect an MCP client, discover Otto's tools and handle paid intelligence calls.

Otto Intel MCP exposes market intelligence and free tools that prepare unsigned transactions. It uses Streamable HTTP at **[https://mcp.ottoai.services](https://mcp.ottoai.services)**. The same transport also accepts the conventional `/mcp` alias. Adding this server grants no trading authority.

## Connect

Use the instruction for your client.

### Claude Code

```bash theme={null}
claude mcp add otto-intel --transport http https://mcp.ottoai.services
```

### Claude Desktop

Customize → Connectors → + → Add custom connector → paste [https://mcp.ottoai.services](https://mcp.ottoai.services).

### Cursor

```json theme={null}
{
  "mcpServers": {
    "otto-intel": {
      "url": "https://mcp.ottoai.services"
    }
  }
}
```

### Codex CLI

```bash theme={null}
codex mcp add otto-intel --url https://mcp.ottoai.services
```

### ChatGPT

Custom MCP connectors require a ChatGPT plan that supports them and may be gated by your workspace admin. In ChatGPT's connector settings, add a custom connector and paste [https://mcp.ottoai.services](https://mcp.ottoai.services). ChatGPT's exact menu path changes over time — follow its current connector/developer settings.

The client handles MCP initialization and the HTTP session. Run `tools/list` for the current tool descriptions and full input schemas, then call the free `otto_catalog` tool.

A first intelligence request can be:

```json theme={null}
{
  "method": "tools/call",
  "params": { "name": "otto_base_season", "arguments": {} }
}
```

This is the inner MCP request; an MCP SDK wraps it in JSON-RPC. A successful tool result contains a text part holding JSON. Inspect `isError` and the parsed body before treating it as data.

## Run your first request from a terminal

The client below uses only public packages and Otto's public MCP. It discovers tools, reads data and preserves the response. It does not load a wallet key, create permission or sign a transaction. Use Bash and Node 22+:

```bash theme={null}
umask 077
set -o noclobber
mkdir otto-agent-start && cd otto-agent-start && \
  npm init -y && \
  npm install --save-exact otto-execute@0.1.3 @modelcontextprotocol/sdk@1.29.0
```

<Accordion title="Copy the client into mcp-call.mjs">
  ```javascript theme={null}
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
  import { createHash } from 'node:crypto';
  import { join } from 'node:path';
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

  const endpoint = 'https://mcp.ottoai.services/mcp';
  const [command, ...args] = process.argv.slice(2);
  const hash = value => createHash('sha256').update(value).digest('hex');
  const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
  const load = path => JSON.parse(readFileSync(path, 'utf8'));
  class InputError extends Error {}
  const digest = request => hash(JSON.stringify({
    endpoint: request.endpoint, tool: request.tool, arguments: request.arguments,
    paymentSha256: request.paymentSha256,
  }));
  let client, directory, save, attempted = false, payment;

  try {
    let request;
    if (command === 'inspect' && args.length === 1) {
      directory = args[0];
      request = { endpoint, tool: 'tools/list', arguments: {}, paymentSha256: null };
    } else if (command === 'call' && args.length === 3) {
      directory = args[2];
      request = { endpoint, tool: args[0], arguments: load(args[1]), paymentSha256: null };
    } else if (command === 'retry' && args.length === 3) {
      directory = args[2];
      request = load(join(args[0], 'request.json'));
      if (!object(request) ||
          !(request.paymentSha256 === null ||
            (typeof request.paymentSha256 === 'string' && /^[a-f0-9]{64}$/.test(request.paymentSha256))) ||
          request.requestSha256 !== digest(request)) {
        throw new InputError('SAVED_REQUEST_CHANGED');
      }
      payment = load(args[1]).x_payment;
      if (typeof payment !== 'string' || payment.length === 0 || payment.length > 32768) {
        throw new InputError('PAYMENT_FILE_INVALID');
      }
      if (request.paymentSha256 !== null && request.paymentSha256 !== hash(payment)) {
        throw new InputError('PAYMENT_CHANGED');
      }
    } else {
      throw new InputError('USAGE: inspect DIR | call TOOL ARGS.json DIR | retry PRIOR_DIR PAYMENT.json DIR');
    }

    if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(directory ?? '')) {
      throw new InputError('OUTPUT_NAME_INVALID: use a new simple directory name inside the current folder');
    }
    if (request.endpoint !== endpoint || !object(request.arguments) ||
        'x_payment' in request.arguments ||
        !(command === 'inspect' ? request.tool === 'tools/list' : /^otto_[a-z0-9_]+$/.test(request.tool))) {
      throw new InputError('REQUEST_INVALID');
    }

    // Refuses an existing directory or symlink. Each attempt keeps its own evidence.
    mkdirSync(directory, { mode: 0o700 });
    save = (name, value) => writeFileSync(join(directory, name),
      JSON.stringify(value, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
    request = { endpoint, tool: request.tool, arguments: request.arguments,
      paymentSha256: payment ? hash(payment) : null,
      startedAt: new Date().toISOString() };
    request.requestSha256 = digest(request);
    save('request.json', request);

    client = new Client({ name: 'otto-public-example', version: '1.0.0' });
    await client.connect(new StreamableHTTPClientTransport(new URL(endpoint), {
      // Do not forward a payment through a redirect or automatically reconnect a request.
      fetch: (url, init) => fetch(url, { ...init, redirect: 'error' }),
      reconnectionOptions: { maxRetries: 0, initialReconnectionDelay: 1000,
        maxReconnectionDelay: 1000, reconnectionDelayGrowFactor: 1 },
    }));

    attempted = true;
    if (command === 'inspect') {
      const result = await client.listTools();
      save('tools.json', result);
      const summary = { outcome: 'tools_received', count: result.tools.length,
        morePages: Boolean(result.nextCursor), finishedAt: new Date().toISOString() };
      save('summary.json', summary);
      console.log(JSON.stringify({ directory, ...summary }));
    } else {
      // One call only. Paid delivery may outlive a normal MCP client's 60-second timeout.
      const result = await client.callTool({ name: request.tool, arguments: {
        ...request.arguments, ...(payment ? { x_payment: payment } : {}),
      } }, undefined, { timeout: 14 * 60_000 });
      save('response.mcp.json', result);
      const text = result.content.filter(part => part.type === 'text');
      if (text.length !== 1) throw new InputError('RESPONSE_TEXT_INVALID');
      const body = JSON.parse(text[0].text);
      if (!object(body)) throw new InputError('RESPONSE_JSON_OBJECT_REQUIRED');
      save('response.json', body);
      const refused = result.isError === true || Boolean(body.error);
      const summary = { outcome: refused ? 'tool_refused' : 'result_received',
        paymentPresented: Boolean(payment),
        settlementMetadataPresent: Boolean(result._meta?.['com.ottoai/payment-response']),
        finishedAt: new Date().toISOString() };
      save('summary.json', summary);
      console.log(JSON.stringify({ directory, ...summary }));
      if (refused) process.exitCode = 2;
    }
  } catch (error) {
    // Never print upstream exception text: it can contain request or authorization data.
    const summary = { outcome: 'local_transport_or_protocol_error',
      reason: error instanceof InputError ? error.message :
        (error?.code === 'EEXIST' && !save ? 'OUTPUT_EXISTS: choose a new directory' : 'CHECK_INPUT_AND_SAVED_RESPONSE'),
      toolCallAttempted: attempted, delivery: attempted ? 'unknown' : 'not_attempted',
      finishedAt: new Date().toISOString() };
    if (save) { try { save('error.json', summary); } catch {} }
    console.error(JSON.stringify(summary));
    process.exitCode = 1;
  } finally {
    if (client) { try { await client.close(); } catch {} }
  }
  ```
</Accordion>

Create the arguments, inspect the available schemas and make the example read:

```bash theme={null}
printf '{}\n' > args.json
node mcp-call.mjs inspect tools-1
node mcp-call.mjs call otto_catalog args.json catalog-1
node mcp-call.mjs call otto_base_season args.json read-1
```

Each output directory must be new. `tools-1/tools.json` contains the discovered schemas; `morePages` tells you whether the server returned another page. Every tool call saves:

* `request.json`: the exact tool, original arguments, timestamp and request fingerprint.
* `response.mcp.json`: the complete MCP result, including payment metadata.
* `response.json`: its parsed JSON body, ready for your agent to use.
* `summary.json`: whether a result or tool refusal was received.

Exit **0** means a parseable result was saved; inspect its actual freshness, coverage and contents before using it. Exit **2** means the saved response is a tool refusal or payment challenge. Exit **1** means a local, transport or protocol failure; `error.json`, when it could be written, records whether a call was attempted. None of these outcomes alone proves transaction settlement.

Keep the folders private: they contain your inputs and purchased data. The Unix permissions above do not configure Windows access controls; use a private workspace there. Never put keys or Otto service credentials in arguments files. Discovering a protected delegation tool does not grant access to it.

### Complete a paid read

If `read-1/response.json` contains `error: "payment_required"`, validate it with the installed public client:

```bash theme={null}
npx --no-install otto-execute x402 --challenge read-1/response.json
```

Check the recipient, token, network, amount and authorization window. Version 0.1.3 pins this `base-season` example to **0.002 USDC on Base**. An unexpected challenge is refused; do not change the verifier to make it pass. Its fixed price table covers four no-argument intelligence routes, not Otto's entire catalog. Other routes use the [SDK integration below](#pay-for-an-intelligence-call).

To purchase the read, load your payment wallet key through your local secret manager or this hidden prompt. The wallet needs Base USDC; it does not need Base ETH for this payment.

```bash theme={null}
read -r -s -p 'Payment wallet private key (hidden): ' OTTO_EXECUTE_PRIVATE_KEY
printf '\n'
export OTTO_EXECUTE_PRIVATE_KEY
npx --no-install otto-execute x402 --challenge read-1/response.json --sign > payment.json
unset OTTO_EXECUTE_PRIVATE_KEY
```

Continue only if signing succeeded and `payment.json` contains `x_payment`. That file is a spend authorization: keep it private and never paste it into an agent conversation. The `noclobber` setting refuses to overwrite an existing authorization; preserve it for recovery instead of signing again. Send it with the original saved request:

```bash theme={null}
node mcp-call.mjs retry read-1 payment.json paid-1
```

Check both `paid-1/response.json` and `paid-1/response.mcp.json`. The latter preserves `_meta["com.ottoai/payment-response"]` as the original `{ header, value }` when supplied. A receipt is separate from whether the expected data arrived; `settlementMetadataPresent` does not validate that receipt.

If delivery is interrupted, make one explicit retry from the paid attempt, using the **same payment file** and a new output folder:

```bash theme={null}
node mcp-call.mjs retry paid-1 payment.json paid-2
```

The helper checks the saved request fingerprint and, after the first paid attempt, the payment fingerprint. It restores the original arguments and makes one call; it neither signs again nor retries automatically. A same-authorization retry can still settle the original payment if it has not settled yet. It is not a free execution retry. For unresolved or expired delivery, follow [the recovery guidance](#save-receipts-and-retry-safely).

### Continue from data to a verified plan

Continue with the complete [Base swap walkthrough](/acp-swarm/execution-seam#prepare-a-base-swap). It uses a separate `otto-swap` folder and saves your own account, exact input and independently chosen minimum output **before** requesting an unsigned plan. Follow that walkthrough from setup onward: its `envelope.json`, `intent.json` and `prepare.json` stay together through verification and any later signing.

Preparation and verification move no funds. The walkthrough's [separate signing and submission step](/acp-swarm/execution-seam#sign-and-submit) uses your own EOA, with its own USDC and ETH gas. This public client does not connect an external agent to the app's delegated Coinbase account. Permission management, supported execution and moving funds out retain the [account-specific boundaries](/account-and-settings/accounts-and-permissions).

## Intelligence tools

| Tool                            | What it returns                                                                                                                                                                                                                                                                 | Price (USDC) |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----------: |
| `otto_crypto_news`              | Read importance-ranked crypto headlines with source links and publication times, plus a sentiment-scored market brief.                                                                                                                                                          |        0.001 |
| `otto_news_recaps`              | Read a concise crypto market recap with ranked stories, source links where available, analysis window and freshness.                                                                                                                                                            |        0.003 |
| `otto_tokenized_equities`       | Scan the live registry of US equities and ETFs tokenized on Robinhood Chain (4663): symbol, canonical token address, decimals, the executable buy price a live route returns, the catalog reference price and the deviation between them, plus a verifiable tradability signal. |        0.001 |
| `otto_rh_season`                | Scan rug-screened Robinhood Chain (4663) movers with honeypot filters and per-token risk flags.                                                                                                                                                                                 |        0.001 |
| `otto_equity_intel`             | Read SEC-EDGAR fundamentals and filings context for a US ticker: revenue and net-income trends, margins, EPS, leverage, recent 10-K/10-Q/8-K filings, and a guarded AI summary.                                                                                                 |        0.003 |
| `otto_insider_trades`           | Read parsed SEC Form 4 insider transactions for a US ticker, including purchase/sale counts, dollar values, roles, and net P/S balance.                                                                                                                                         |        0.003 |
| `otto_institutional_holdings`   | Read the latest SEC 13F-HR top positions for an institutional manager by CIK or manager ticker, with amendments applied.                                                                                                                                                        |        0.003 |
| `otto_equity_smart_money`       | Read a public-domain SEC bundle for a US ticker: Form 4 insider activity plus trailing-twelve-month XBRL fundamentals.                                                                                                                                                          |        0.006 |
| `otto_equity_smart_money_brief` | Read one guarded AI brief over observed SEC Form 4 flow, 13D/13G ownership events, and fundamentals for a US ticker.                                                                                                                                                            |         0.10 |
| `otto_base_season`              | Scan quality-screened Base tokens with current social-intelligence and trusted-KOL context.                                                                                                                                                                                     |        0.002 |
| `otto_pm_markets`               | Read the highest-volume open Polymarket markets with live outcome probabilities, bid/ask context, liquidity, volume, and end dates.                                                                                                                                             |        0.001 |
| `otto_pm_crypto`                | Read high-volume open BTC, ETH, and crypto Polymarket markets with current outcome probabilities and market context.                                                                                                                                                            |        0.001 |
| `otto_stock_pools`              | Find the DEX pools a tokenized US stock or ETF is actually a member of, on Robinhood Chain, Base, BNB Chain or Solana.                                                                                                                                                          |        0.001 |

Use `ticker` for SEC issuer reads, `manager` for institutional holdings, optional `thesis` for tokenized equities, and a network-qualified `stock` for pool discovery. Exact accepted inputs come from `tools/list`.

Read the result's freshness and coverage fields. [SEC filing methodology](/acp-swarm/sec-filings-methodology) explains amendments, missing data and what filing figures mean.

## Pay for an intelligence call

On the hosted server, the first successful eligible intelligence call from your network address is free. A shared network may already have used it. Later calls return `isError: true` and a JSON body with `error: "payment_required"`.

1. Inspect `payment_required.accepts`: the network, asset, recipient, amount and authorization lifetime. `payment.requirement` is the same requirement in a flatter form; `payment_required_header` is the base64 header form.
2. Sign the selected requirement locally with a wallet holding USDC on Base.
3. Retry the **same tool arguments** with the encoded authorization in `x_payment`.

For an existing MCP integration using the x402 SDK, this creates the payment value from the parsed challenge `result`. Install `@x402/core`, `@x402/evm` and `viem`; load the wallet key through your local secret configuration, never through an agent conversation.

```typescript theme={null}
import { x402Client } from '@x402/core/client';
import { encodePaymentSignatureHeader } from '@x402/core/http';
import { ExactEvmScheme } from '@x402/evm/exact/client';
import { toClientEvmSigner } from '@x402/evm';
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY as `0x${string}`);
const signer = toClientEvmSigner(account, createPublicClient({
  chain: base, transport: http(),
}));
const client = new x402Client((_version, requirements) => {
  const accepted = requirements.find(r => r.network === 'eip155:8453');
  if (!accepted) throw new Error('No Base payment option');
  return accepted;
});
client.register('eip155:8453', new ExactEvmScheme(signer));

// Review and approve the challenge's recipient, asset and amount first.
const payload = await client.createPaymentPayload(result.payment_required);
const x_payment = encodePaymentSignatureHeader(payload);
// Call the same MCP tool with { ...originalArguments, x_payment }.
```

The authorization expires after the window in the challenge, currently 300 seconds. An EIP-3009 USDC payment needs no Base ETH in the paying wallet. Trading from an EOA has separate gas requirements.

### Save receipts and retry safely

Keep the complete MCP response, original arguments and signed payment payload. Paid results carry settlement metadata under `_meta["com.ottoai/payment-response"]` when available. Check that the expected result arrived as well as the receipt.

If delivery drops, promptly retry the exact same tool input with that same signed payload. Signing again creates another possible payment. The paid rail can replay a stored response without a second settlement while its delivery window permits; an authorization cannot be reused for a different input. For `settlement_unconfirmed`, follow the response's retry instruction. For `delivery_replay_expired` or an unresolved paid delivery, retain transaction evidence and [contact support](/support-and-feedback/getting-help). Replay is not unlimited and a refund is not automatic.

## Run the intelligence server locally

The published package is [otto-intel-mcp@0.1.3](https://www.npmjs.com/package/otto-intel-mcp). It needs Node 22+ and a Base-USDC wallet key in `X402_PRIVATE_KEY`. Inject that variable using your MCP client's local secret configuration, then configure this stdio command:

```json theme={null}
{
  "mcpServers": {
    "otto-intel-local": {
      "command": "npx",
      "args": ["--yes", "otto-intel-mcp@0.1.3"]
    }
  }
}
```

This process must receive `X402_PRIVATE_KEY` in its environment. It automatically pays every intelligence call from that wallet; it has no hosted free-call allowance. Optional settings are `X402_RPC_URL` and `X402_TIMEOUT_MS` (default 30000). The published package's default is intelligence tools; use the hosted server for the constructors described below.

## Free transaction tools and menus

`otto_prepare_*` tools build unsigned action plans. [Prepare and sign transactions](/acp-swarm/execution-seam) covers their schemas, checks and the supported Base EOA execution client.

These menus also remain free:

| Tool                           | Purpose                                                                       |
| ------------------------------ | ----------------------------------------------------------------------------- |
| `otto_catalog`                 | Discover the current HTTP catalog and prices                                  |
| `otto_x_recipes`               | Read the X Layer recipe menu                                                  |
| `otto_delegation_fence_status` | Inspect the current Coinbase delegation policy and whether minting is enabled |

Public MCP access does not unlock protected delegated submission or private permission-management tools. Those are authenticated server-to-server operations used by Otto's app. The public permission CLI manages permission; it is not a general delegated execution client.

### Crypto news and recaps

The source candidate adds `otto_crypto_news` (GET `/crypto-news`, $0.001 USDC) and `otto_news_recaps` (GET `/news-recaps`, $0.003 USDC). Check the deployed `tools/list` for availability. Both accept `{}`; hosted paid retries add `x_payment`.

Crypto news is paid-only and leaves the free allowance unspent. News recaps can use the allowance when a cached result is available; a cold or unavailable read returns a payment challenge without generating content for free. Stale-but-servable cached recaps can use the allowance and retain their upstream degraded and freshness indicators; the backend refuses results beyond its three-hour hard age ceiling. Responses preserve upstream source links, missing-link indicators, analysis windows and freshness metadata.
