MCP (agents)
Connect Claude, Cursor, Grok, or Inspector to AXIS so an agent can load symbols, edit Pine, run scripts, and read results.
This page
Abstract
AXIS speaks Model Context Protocol so a coding agent can read and write the product: load a symbol, edit the Pine buffer, run a script, inspect strategy results, toggle panels, manage alerts and the library.
Two planes:
| Plane | Live AXIS tab? | Typical use |
|---|---|---|
| Worker | No | Health, POST /api/run, cloud scripts, on-chain / market proxy |
| App | Yes — this browser session | Chart, editor, indicators, alerts, watchlist, library, results, chrome |
Auth is a Worker API key (pn_…), the same Bearer used for cloud script storage. Secrets (exchange credentials, API keys) are redacted from snapshots.
Operator contract and tool schemas: MCP server.
Conceptual model
Rendering…
Connect this tab
- Mint a key:
axis keys create(needsADMIN_TOKEN). - Studio → Settings → General → Worker (cloud + MCP) — paste Worker URL (
https://worker.axis.hoox.shorhttp://127.0.0.1:8787) and thepn_…key. Data tab is exchange credentials only. - Studio → Settings → General → MCP — leave Connect this tab to MCP on (default).
- Confirm the hint shows
Bridge: open(orconnecting).idlewith “No Worker API key” means step 2 is missing.
The PWA mints a short-lived one-time ticket over HTTP (Authorization: Bearer) then opens wss://…/api/mcp/bridge?ticket=… so the long-lived pn_… key never appears in the WebSocket URL. The Worker partitions the session by a hash of the key — another user’s key cannot attach to your tab.
Turn the toggle off if you do not want agents driving this browser.
Status-bar indicator
The status bar shows an MCP capsule with two dots:
- Session dot — bridge socket state (green open, amber connecting,
red error/off, dim idle) plus how many PWA tabs the Worker sees on your
key's session (
MCP · 2 tabs). The count polls every 20 seconds while connected. - Activity dot — lights up while an agent is actively driving this tab
(any
app_invokereceived in the last few seconds). Hover for the last capability and when it ran.
Click the capsule to open Settings → MCP.
Note: MCP clients (Claude, Cursor, …) call POST /mcp statelessly, so there
is no per-client presence — the capsule tracks bridge + tab attachment and
live invoke activity, which is what app_* tools need.
MCP in System Logs
Every bridge event lands in System Logs (topbar Logs) with source
mcp: connects, disconnects, session tab-count changes, and one line per
agent invoke (drawings.add → ok · 84ms, errors with their code). Filter the
strip by source (mcp) or level, or query programmatically with
logs.get ({ source?, level?, limit? }).
New app versions prompt via an update banner (version check every 2 minutes, on window focus, and when the bridge reconnects) — no manual reload hunting.
Connect a client
Print a snippet:
axis mcp config --key pn_…
Endpoint: https://worker.axis.hoox.sh/mcp (or your wrangler origin + /mcp).
Remote HTTP (Inspector, Cursor, clients with URL + headers)
{
"mcpServers": {
"axis": {
"url": "https://worker.axis.hoox.sh/mcp",
"headers": { "Authorization": "Bearer pn_…" }
}
}
}
GET /mcp is public discovery (tool names only). Tool calls require Bearer.
Stdio (axis mcp)
For clients that only spawn a local process:
axis mcp --key pn_…
# or AXIS_API_KEY=pn_… axis mcp
axis mcp config --stdio --key pn_…
The CLI forwards newline-delimited JSON-RPC from stdin to POST /mcp.
What to ask an agent
Worker-only (no tab):
- “Call
axis_healthand tell me if D1 and MCP are on.” - “List my cloud scripts.” (
axis_scripts_list) - “Run this Pine against these OHLCV rows.” (
axis_run)
Live tab (app_session_status → connected):
- “Load BTCUSDT 1h and tell me bar count and last close.” (
chart.loadthenapp_getbars) - “Put this indicator in the editor and run it.” (
editor.set+editor.run) - “Open the alerts panel and list alerts.” (
panels.set+alerts.list) - “Screenshot the panes.” (
chart.screenshot— PNG base64 of the Lightweight Charts panes) - “My chart seems a bar behind — what's wrong?” (
chart.lag— the verdict compares the chart tail to the venue clock, the tick, and the engine series) - “Where are the holes in my bars?” (
chart.bars— bounded tail/head rows + a missing-slot map) - “Is the engine's series advancing, or is the socket wedged?”
(
stream.get+engine.series) - “Tick the engine now / repair the stale series / reset the socket.”
(
engine.tick·engine.repair·stream.reset— the last one drops the socket and subscriptions so the next tick re-subscribes) Start withapp_capabilitiesif the client has not cached the catalog.
Capabilities at a glance
Pass these ids to app_invoke as capability, with a JSON payload.
| Domain | Ids |
|---|---|
| App | app.snapshot, app.get, app.set, app.command, app.shortcut, app.event |
| Chart | chart.get / set / load / reload / live / layout / theme / type / screenshot / zoom / bars / lag |
| Editor | editor.get / set / run / diagnostics |
| Scripts | indicators.list / add / remove / update / run (remove detaches chart panes like the Layers panel; code edits need run after) |
| Alerts | alerts.list / create / update / remove |
| Watchlist | watchlist.get / add / remove / lists |
| Library | library.list / read / write / remove |
| Results / logs | results.get, logs.get / append / clear |
| Chrome | panels.get / set, theme.get / set, plugins.list / activate |
| Workspace | workspace.export / import |
| Drawings | drawings.list / add / update / remove / clear / tool |
| Settings | settings.get / set |
| Engine | engine.get / series (read the engine's own series bounds + staleness) · engine.tick / repair (force a drive tick / stale-series repair) |
| Stream | stream.get (route, engine socket, subscriptions, feed chips) · stream.reset (drop socket + subscriptions, probes a re-subscribe) |
| Status | status.get |
app_set paths (anything else is PATH_DENIED): symbol, interval, source, engine, exchange, theme, chartType, uiScale, live.active, live.preferAfterLoad, editor.doc, editor.open, watchlist.open.
Example payloads:
{ "capability": "chart.load", "payload": { "symbol": "ETHUSDT", "interval": "15m" } }
{ "capability": "editor.set", "payload": { "doc": "//@version=6\nindicator(\"RSI\")\nplot(ta.rsi(close, 14))" } }
{ "capability": "alerts.create", "payload": { "name": "BTC 100k", "kind": "price_cross", "symbol": "BTCUSDT", "params": { "price": 100000 } } }
{ "capability": "drawings.add", "payload": { "kind": "trend", "points": [{ "time": 1700000000, "price": 67000 }, { "time": 1700003600, "price": 67600 }] } }
{ "capability": "settings.set", "payload": { "settings": { "interval": "1h", "historyBars": 5000 } } }
{ "capability": "chart.lag", "payload": {} }
{ "capability": "chart.bars", "payload": { "limit": 20, "gaps": true } }
{ "capability": "engine.tick", "payload": {} }
{ "capability": "stream.reset", "payload": { "resubscribe": true } }
Full argument tables: MCP server.
Limits
- No tab → Worker tools still work;
app_*returnsSESSION_OFFLINE. MCP_BRIDGEunbound →NO_MCP_BRIDGE(add the Durable Object inwrangler.tomland deploy).axis_requestis not an open proxy — only AXIS JSON APIs. Stream, OAuth device-flow, and/mcprecursion are blocked.- Agents must not invent Pine APIs (
TV.set,pine.chart.setTheme, …). See AGENTS.md in the repo. - MCP is not HOOX order routing. Alerts remain local (browser / webhook).
Troubleshooting
| Symptom | Check |
|---|---|
401 NO_KEY / INVALID_KEY | Bearer on /mcp; mint with axis keys create |
503 API_KEYS_REQUIRED | D1 bound without API_KEYS KV — axis setup kv |
SESSION_OFFLINE | Tab open, MCP toggle on, same pn_… as the client |
Bridge: idle / no API key | Settings → General → Worker (cloud + MCP) |
Bridge: error | Worker URL reachable; CORS; MCP_BRIDGE deployed |
| App command unknown | Palette has not mounted yet — use chart.* / editor.* ids instead |
In-page test (browser console on the AXIS origin):
await window.__AXIS_MCP__.invoke('chart.get')
See also
- MCP server — tools, resources, prompts, allowlist
- AXIS CLI
- Auth
- Manager and settings