pyon-mcp
MCP (Model Context Protocol) server for the Pyon trading platform. It lets AI agents - Claude Code, Claude Desktop, Codex, or any MCP client - drive Pyon end to end: search markets, generate research, build and edit node-graph strategies with AI, run backtests, diagnose problems, and optimize parameters with 2-D sweeps.
Runs over stdio, talks to api.pyon.io, and needs only Node 18+.
Getting an API key
- Sign in at app.pyon.io.
- Open Account > API Access.
- Create a personal access token. It looks like
pyk_....
Set it as the PYON_API_KEY environment variable wherever the server runs.
Setup
Claude Code
claude mcp add pyon -e PYON_API_KEY=pyk_... -- npx -y pyon-mcp
Or, from a local checkout:
npm install && npm run build
claude mcp add pyon -e PYON_API_KEY=pyk_... -- node /path/to/pyon-mcp/dist/index.js
Claude Desktop
Add to claude_desktop_config.json (Settings > Developer > Edit Config):
{
"mcpServers": {
"pyon": {
"command": "npx",
"args": ["-y", "pyon-mcp"],
"env": {
"PYON_API_KEY": "pyk_..."
}
}
}
}
Codex
Add to ~/.codex/config.toml:
[mcp_servers.pyon]
command = "npx"
args = ["-y", "pyon-mcp"]
env = { PYON_API_KEY = "pyk_..." }
Start here: get_capabilities
Pyon turns a strategy description into a node graph literally. An indicator name the engine does not
know, or a threshold outside an indicator's range, produces a strategy that backtests to zero trades
and looks broken for no visible reason - an RSI > 120 entry can never fire, because RSI is bounded
0-100.
So call get_capabilities before writing any strategy description, edit instruction, or sweep
bound. It returns the real catalog: 61 market indicators with their value ranges and indicatorParams,
9 portfolio indicators, 8 operators, 8 trigger types, 13 action types, the 5 supported timeframes, the
3 quantityType modes, 7 option strategyType values, and the 47 tradable tickers.
The catalog is fetched from GET /api/capabilities and falls back to a copy bundled with this server
if that endpoint is unavailable. Every response names which source it used. The same catalog is
readable as markdown in the pyon://capabilities resource.
Tools
Every input schema is strict: an unknown or misspelled parameter (timeFrame, start_date,
limit) is rejected with an explicit error rather than silently ignored. Every rejection message
states what IS allowed.
What each tool does
Validation rules worth knowing
- Ids are UUIDs. A strategy name will be rejected; the message points at
list_strategies. - Dates are
YYYY-MM-DDreal calendar dates, never in the future.2025-02-30,2024-1-5,01/02/2024and full timestamps are all rejected. - Backtest windows need
endDateafterstartDateand at least 7 days between them. A 1d strategy tested over fewer than 300 days still runs, but the result carries awarningexplaining that the window, not the strategy, may be what the metrics are measuring. - Timeframes are exactly
1m,5m,15m,1h,1d.1D,daily,1wand30mare rejected - these are the five bar sizes the engine resolves. - Sweep axes must describe a real range (
xMax > xMin,yMax > yMin) and must not point at the same node id and config field, which would test one dimension twice. - Prompts for
create_strategy,edit_strategy,create_researchand the optionaldiagnose_strategyquestion need at least 10 characters, because a vague prompt produces a vague strategy.
Resources
Errors you may see
- 401 - invalid or revoked API key. Create a new one in Account > API Access at app.pyon.io.
- 402 - a plan limit was hit; the message explains which. Upgrade at app.pyon.io/app/account/billing.
- Timeouts - long AI jobs keep running server-side; the timeout message includes the job id
to check with
get_job_status. - Invalid arguments - the message names the parameter and the allowed values. Fix and retry; these never reach the API.
Development
npm install
npm run build # tsc -> dist/
npm run smoke # offline, keyless: tools/list, JSON Schema completeness, and the validation tables
npm run check # build + smoke
The compiler runs at the strictest settings the code satisfies: strict, noUncheckedIndexedAccess,
exactOptionalPropertyTypes, verbatimModuleSyntax, isolatedModules, noImplicitOverride,
noImplicitReturns, noFallthroughCasesInSwitch, noUnusedLocals, noUnusedParameters,
allowUnreachableCode: false and allowUnusedLabels: false. noPropertyAccessFromIndexSignature is
deliberately left off: it only forces process.env["PYON_API_KEY"] bracket syntax and catches nothing
here. Wire payloads are read through the case-insensitive helpers in src/format.ts, which return
unknown, so every tool has to narrow a value before putting it in one of the interfaces in
src/types.ts - a backend field rename surfaces as a compile error rather than a missing JSON key.
scripts/smoke.mjs runs entirely offline. It asserts the 13 tools and 2 resources are registered,
that every published JSON Schema names its parameters and sets additionalProperties: false, and it
drives every tool's zod schema with a table of bad inputs that must be rejected and good inputs that
must parse. It exits non-zero on any failure.