Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/backtest-kit/ai-trading-mcp/llms.txt

Use this file to discover all available pages before exploring further.

AI Trading MCP runs as two completely separate OS processes that communicate over a local HTTP bridge. The boundary is deliberate: Claude Code and the trading engine have independent lifecycles, and crashing or restarting one cannot corrupt the other’s state. Understanding this model is essential before going live.

The Two-Process Model

┌─────────────────────────────────────────────────────────┐
│  Claude Code process                                    │
│                                                         │
│  claude --dangerously-skip-permissions \                 │
│         --mcp-config ./mcp.servers.json                 │
│       │                                                 │
│       │  stdio JSON-RPC                                 │
│       ▼                                                 │
│  stdio MCP server  (npx -y @backtest-kit/mcp@latest)    │
│  · holds NO trading state                               │
│  · forwards every tool call over HTTP                   │
└───────────────────┬─────────────────────────────────────┘

                    │  HTTP  127.0.0.1:60051
                    │  { tool, args } → { result, error }

┌───────────────────▼─────────────────────────────────────┐
│  Trading process  (@backtest-kit/cli)                   │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │  HTTP bridge  serve()  127.0.0.1:60051           │   │
│  └────────────┬──────────────────┬──────────────────┘   │
│               │ get_status       │ open/close_position  │
│               ▼                  ▼                       │
│  ┌────────────────────┐  ┌────────────────────────────┐ │
│  │  manual_mcp        │  │  Engine                    │ │
│  │  StatusController  │  │  candles · signals · TP/SL │ │
│  │  + Telegram feed   │  │  risk · validation         │ │
│  │  (last 15 posts,   │  │  persistence               │ │
│  │   text + images)   │  └──────────────┬─────────────┘ │
│  └────────────────────┘                 │               │
│                                         ▼               │
│                              ┌─────────────────────┐    │
│                              │  Broker             │    │
│                              │  paper fills  OR    │    │
│                              │  Binance spot       │    │
│                              └──────────┬──────────┘    │
└─────────────────────────────────────────┼───────────────┘
                                          │  ccxt

                                   Binance spot API
The stdio MCP server (npx -y @backtest-kit/mcp@latest) lives inside Claude Code’s process tree. It is stateless: it knows nothing about open positions, candle history, or the Telegram feed. Its only job is to receive JSON-RPC tool calls from Claude over stdio and relay them to the trading process over HTTP. The trading process (@backtest-kit/cli, started with npm start) owns all state: the engine loop for every symbol, the Telegram scraper, the broker adapter, the persistence layer, and the audit trail. It exposes the HTTP bridge on 127.0.0.1:60051 and remains running regardless of what happens in the Claude Code session.

The HTTP Bridge

The bridge is started by the serve() call at the bottom of config/setup.config.ts:
import { serve } from '@backtest-kit/mcp';

// ... persistence wiring ...

serve();
By default it binds to 127.0.0.1:60051. Both values are overridable via environment variables:
VariableDefaultPurpose
CC_MCP_HOST127.0.0.1Interface the bridge listens on
CC_MCP_PORT60051Port the bridge listens on
The stdio MCP server reads these same variables to know where to connect, so changing them in your .env propagates automatically to both ends.

The Error Envelope

Transport success is not operation success. When the engine rejects a tool call — for example, open_position against a symbol that already holds an open position — the HTTP response still returns 200. The result payload carries an error field:
  • If the operation succeeded: error is an empty string.
  • If the operation failed: error contains the engine’s exact rejection message.
The stdio MCP server reads this field and surfaces it to Claude as an isError: true tool result. Claude can read the message and decide whether to retry, close an existing position first, or report the failure to the user. Nothing is silently swallowed.
This envelope pattern means you cannot tell from the HTTP status code alone whether a trade was placed. Always check the error field in integration tests and monitoring scripts.

mcp.servers.json

mcp.servers.json in the repo root is the config file Claude Code reads when you run npm run start:claude:
{
  "mcpServers": {
    "backtest-kit-mcp": {
      "command": "npx",
      "args": ["-y", "@backtest-kit/mcp@latest"]
    }
  }
}
Claude Code launches npx -y @backtest-kit/mcp@latest as a child process and communicates with it over stdio JSON-RPC. The @latest tag means you always pick up the current published version of the MCP server package without a local install step.

MCP Schema Registration

The manual_mcp schema is registered in packages/agent/src/config/setup.ts using addMCPSchema from backtest-kit. This is the single integration point that wires the Telegram feed and portfolio state into the get_status tool result:
import { addMCPSchema } from "backtest-kit";
import ioc from "../lib";

addMCPSchema({
  mcpName: "manual_mcp",
  async getMessages(context, when, mcpName) {
    return await ioc.statusControllerService.getStatus(context, when, mcpName);
  },
});
ioc.statusControllerService.getStatus composes everything the model sees on every get_status call: the engine’s portfolio snapshot (one message per symbol), command history, and the last 15 Telegram posts including any chart screenshot image blocks. config/loader.config.ts imports @pro/agent and @pro/main into the CLI runtime:
import "@pro/agent";
import "@pro/main";
config/alias.config.ts maps those package names to the compiled output so the CLI can resolve them at start-up:
export default {
  "@pro/agent": require("../packages/agent/build/index.cjs"),
  "@pro/main":  require("../packages/main/build/index.cjs"),
}
This is why npm run build must be run before npm start — the aliases point to build artifacts that don’t exist until the workspace packages are compiled.

Concurrency: the queued() Wrapper

StatusControllerService wraps its getStatus implementation in a queued() utility from functools-kit. This means only one get_status fetch can be in flight at a time — if Claude sends a second call while the first is still awaiting the Telegram fetch, the second call lines up and waits rather than spawning a parallel GramJS request. This prevents concurrent calls from stampeding the Telegram client, which is a single persistent connection. A 90-second timeout guards against a hung fetch; if it fires, the connection is torn down and rebuilt on the next call, and the agent receives a clean error it can act on.

Persistence Wiring

config/setup.config.ts configures the persistence backend for each engine subsystem. In live mode, state survives process restarts:
SubsystemLive modeBacktest / paper mode
Signals (SessionLive)Persisted to dump/data/Local (in-process)
StoragePersisted to dump/data/In-memory
RecentPersisted to dump/data/In-memory
NotificationsPersisted to dump/data/In-memory
MemoryPersisted to dump/data/Local
StatePersisted to dump/data/Local
Every engine event also streams into dump/report/*.jsonl — one file each for live, performance, max_drawdown, and highest_profit. Restart the trading process and the open position, its OCO brackets, and its full history are still present.

Why the Two-Process Design Matters

Splitting the agent and the trading engine into separate processes has two critical consequences:
  1. Claude Code can restart without affecting the trading process. If you close the Claude Code session, lose your terminal, or restart the agent, all open positions remain active. The trading process keeps running, its engine loop keeps ticking, and TP/SL orders remain on the book (in live mode). Nothing is liquidated by an agent crash.
  2. The trading process can restart without losing state. Because live-mode subsystems are persisted to disk, restarting npm start after a server reboot or a dependency update brings the engine back to the exact state it left: same open positions, same bracket orders, same signal history. The model will see the recovered state on its next get_status call.
This boundary is the reason the architecture diagram shows the HTTP bridge as the only coupling between the two sides. Every other connection — ccxt, GramJS, the persistence layer — belongs entirely to the trading process and is invisible to the stdio MCP server.

Build docs developers (and LLMs) love