npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@backtest-kit/mcp

v18.3.1

Published

Model Context Protocol server for backtest-kit: lets an LLM agent observe the live trading portfolio and open or close positions through guarded tools

Downloads

1,484

Readme

🤖 @backtest-kit/mcp

Model Context Protocol server for backtest-kit. Lets an LLM agent (Claude, or any MCP client) watch your live trading portfolio and open or close positions — through three guarded tools, while the trading engine keeps every level, limit and validation on its side.

Ask DeepWiki npm TypeScript

📚 Docs · 🌟 Reference implementation · 🐙 GitHub

npm install @backtest-kit/mcp backtest-kit @modelcontextprotocol/sdk

In the trading process — register an MCP schema and expose the HTTP bridge:

import { addMCPSchema } from 'backtest-kit';
import { serve } from '@backtest-kit/mcp';

addMCPSchema({
  mcpName: 'my-mcp',
  strategyName: 'my-strategy', // the strategy whose live instances the agent sees
});

serve(); // HTTP bridge on 127.0.0.1:60051 (CC_MCP_HOST / CC_MCP_PORT)

In the MCP client (e.g. .mcp.json for Claude Code) — run the stdio server:

{
  "mcpServers": {
    "trading-signals": {
      "command": "npx",
      "args": ["@backtest-kit/mcp"],
      "env": { "CC_MCP_HOST": "127.0.0.1", "CC_MCP_PORT": "60051" }
    }
  }
}

The agent gets get_status, open_position and close_position. Your strategy code does not change.


Why

The agent decides when and which way — the engine decides everything else. An LLM given raw exchange API keys is a liability: one hallucinated parameter and it buys the wrong size at the wrong price with no stop. Here the agent's whole vocabulary is three tools, and the open command carries only symbol, position and a human-readable note. Take profit, stop-loss and entry cost are computed by backtest-kit (fixed 50% moonbag TP; hard stop snapped to a 2.5% grid strictly below CC_MAX_STOPLOSS_DISTANCE_PERCENT; cost from the MCP schema). Every command passes the same validation chain — MCP → strategy → risk profiles → actions — as any other signal source, and an open against a symbol that already holds a position is rejected by the engine, not by prompt engineering.

Text-first status built for LLM consumption. get_status returns one message per traded symbol with the current price, invested balance, the queued entry order, the active position with unrealized PnL and the queued close order. Empty slots are stated explicitly ("Entry queue: empty") so the model never has to guess whether a field was omitted or just missing — the difference between an agent that reasons and one that hallucinates. A custom getMessages in the schema can replace or extend the default renderer, including base64 chart images, which map 1:1 onto MCP image content blocks.

Two processes, one contract. The stdio MCP server lives in the agent's world and holds no trading state; it forwards every call over HTTP to the trading process. Handlers always answer 200 — transport success is not operation success — with the outcome in an envelope: error is an empty string on success and the engine's exact message (MCP Error: symbol BTCUSDT is not enabled for trading) on failure, which the tool relays to the agent as an isError result it can read and react to.

  • 🛠️ Three guarded toolsget_status, open_position, close_position; nothing else is exposed.
  • 🧷 Engine-owned levels — moonbag TP/SL and entry cost are computed server-side; the agent cannot override them.
  • 💬 Human-readable portfolio — per-symbol text messages with explicit empty slots; images supported.
  • 🔌 Process isolation — stdio server ↔ HTTP bridge ↔ trading engine; stdout carries only JSON-RPC.
  • At-most-once semantics — commands reuse backtest-kit's commitCreateSignal / commitClosePending machinery.
  • 🧪 TestableIMCPCallbacks (onStatus, onPositionOpen, onPositionClose) fire after each accepted effect with the raw data it was built from.

Configuration

import { setConfig } from '@backtest-kit/mcp';
setConfig({
  CC_MCP_HOST: '127.0.0.1',
  CC_MCP_PORT: 60051,
  CC_MCP_NAME: 'my-mcp',
});

| Variable | Default | Description | |----------|---------|-------------| | CC_MCP_HOST | 127.0.0.1 | Host the HTTP bridge listens on (trading process) and connects to (stdio process) | | CC_MCP_PORT | 60051 | Port of the HTTP bridge | | CC_MCP_NAME | (empty) | MCP schema to use when several are registered; empty = the first registered schema |

Values passed to setConfig() always take precedence over env vars.

When the package runs as the stdio MCP server (npx @backtest-kit/mcp, the backtest-kit-mcp command, or node build/index.mjs), the bridge address can be passed as CLI arguments instead of env vars:

npx @backtest-kit/mcp --host 127.0.0.1 --port 60051
{
  "mcpServers": {
    "trading-signals": {
      "command": "npx",
      "args": ["@backtest-kit/mcp", "--host", "127.0.0.1", "--port", "60051"]
    }
  }
}

| Argument | Overrides | Description | |----------|-----------|-------------| | --host | CC_MCP_HOST | Host of the HTTP bridge to connect to | | --port | CC_MCP_PORT | Port of the HTTP bridge; a non-numeric value is ignored |

Resolution order: CLI arguments → setConfig() → env vars → defaults (127.0.0.1:60051).

CLI arguments apply only in binary mode — an entrypoint guard (helpers/getEntry.ts) makes sure that when the package is imported as a library, the host process's argv never leaks into the configuration.


API reference

| Export | Description | |--------|-------------| | serve(callback?) | Start the HTTP bridge in the trading process (singleshot; safe to call twice). | | getRouter() | The underlying request handler — mount it into your own HTTP server instead of serve(). | | setConfig(config) | Override host/port/name at runtime. | | getConfig() | The current merged configuration (env + any setConfig overrides). | | setLogger(logger) | Replace the internal no-op logger with your own implementation. | | lib | The IoC container (mcpCommandService, mcpPublicService, mcpPrivateService) for advanced wiring and tests. |

Running the package binary (npx @backtest-kit/mcp, the installed backtest-kit-mcp command, or node build/index.mjs) starts the stdio MCP server — that side needs no imports, only CC_MCP_HOST/CC_MCP_PORT pointing at the trading process.


The 3 tools

| Tool | Arguments | What the agent gets | |------|-----------|---------------------| | get_status | — | One message per traded symbol: current price, invested balance, queued entry order, active position with unrealized PnL (% and USD), queued close order. | | open_position | symbol, position (long | short), note | Opens at market price with engine-computed TP/SL/cost. Fails if the symbol is not live-enabled or already has an active position. | | close_position | symbol, note | Queues a market close of the active position. Fails if there is nothing to close. |

Every failure reaches the agent as an isError tool result carrying the engine's exact error message — the agent is expected to call get_status first and react to rejections, not retry blindly.


How it works

agent (Claude / any MCP client)
  └─ stdio JSON-RPC ─ backtest-kit-mcp          (this package, binary)
       tools/*.tool.ts
       └─ MCPCommandService ── HTTP POST ──► serve()   (this package, imported)
                                              routes/mcp.ts
                                              └─ MCPPublicService ─► MCP.* (backtest-kit)
                                                                      └─ Live.commitCreateSignal / commitClosePending

The stdio process never touches trading state — it only speaks HTTP. The trading process registers schemas, runs Live, and answers on /api/v1/mcp/*. Both roles ship in one package: importing it gives you serve(), executing it starts the stdio server (an entrypoint guard makes the side-effect import a no-op in library mode).

HTTP handlers never signal operation failure through status codes. Every response is 200 with:

{ "data": …, "status": "ok",    "error": "",                          "requestId": "…", "serviceName": "…" }
{            "status": "error", "error": "MCP Error: no active position for BTCUSDT" }

MCPCommandService throws when error is non-empty; the tool catches and returns the message to the agent as isError. Transport-level failures (engine down, wrong port) surface the same way via fetchApi's exception.

| Endpoint | Method | Body data | Maps to | |----------|--------|-------------|---------| | /api/v1/mcp/get_status | POST | — | MCP.getStatus(mcpName) | | /api/v1/mcp/commit_position_open | POST | { symbol, position, note } | MCP.commitPositionOpen(dto) | | /api/v1/mcp/commit_position_close | POST | { symbol, note } | MCP.commitPositionClose(dto) | | /api/v1/health/health_check | GET | — | uptime / memory / CPU snapshot |

Request envelope: { clientId, serviceName, userId, requestId, data }. The mcpName is resolved server-side: CC_MCP_NAME if set, otherwise the first registered schema — the agent never needs to know it.


Internal architecture (complete source map)

Public surfacefunctions/serve.function.ts (serve/getRouter), functions/setup.function.ts (setLogger), config/params.ts (setConfig/getConfig), index.ts re-exports + lib container.

Stdio servermain/entry.ts (McpServer + StdioServerTransport, entrypoint-guarded via helpers/getEntry.ts), tools/get_status.tool.ts, tools/open_position.tool.ts, tools/close_position.tool.ts (zod-validated arguments, isError mapping).

HTTP bridgeconfig/router.ts (micro + router + CORS, /api/v1/mcp/* mount), routes/mcp.ts (three POST handlers, always-200 envelope), routes/health.ts.

Service layer (lib/services/):

  • command/MCPCommandService.ts — HTTP client used by the tools (fetchApi against CC_MCP_HOST:CC_MCP_PORT).
  • public/MCPPublicService.ts — server-side entry: resolves mcpName (CC_MCP_NAME or first schema), validates arguments, delegates down.
  • private/MCPPrivateService.ts — thin logging proxy over the backtest-kit MCP singleton.
  • base/LoggerService.ts — no-op by default (keeps stdio stdout clean); swap via setLogger.

DI & configlib/core/{di,provide,types}.ts, lib/index.ts (container bootstrap), utils/omit.ts (log payload trimming).

🤝 Contribute

Fork / PR on GitHub.

📜 License

MIT © tripolskypetr