node_snapshot
The one read tool against a running vike-tradehub node — orders, positions, per-venue equity, recent events — and the tool that settles an ambiguous write.
node_snapshot reads the live state of a running vike-tradehub node. It is the only read-only
tool in vike-cli mcp that talks to a node rather than to a datahub, annotated
readOnlyHint: true and openWorldHint: true in tools/list
(crates/vike-cli/src/cmd/mcp.rs's tools_spec). It needs --node <host:port> and
VIKE_TRADEHUB_OBSERVE_KEY — the observe key, not the control key
(crates/vike-cli/src/cmd/nodekeys.rs's OBSERVE_KEY_ENV).
Input schema
It takes no arguments: "inputSchema": { "type": "object", "properties": {} }. It is also the one
node tool with no reason property — a read has no rationale to file, and the tools/list test in
crates/vike-cli/src/cmd/mcp.rs asserts the property is absent.
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"node_snapshot","arguments":{}}}What comes back
Server::tool_node_snapshot serializes the node's WireSnapshot as-is — "already a flat, serde
projection". The schema is crates/vike-tradehub-client/src/wire.rs's WireSnapshot, a standalone
mirror of the core's rendered snapshot rather than the core type itself, so an internal refactor
cannot silently change what an agent reads:
| Field | What it holds |
|---|---|
seq | per-publish sequence number; 0 means no real frame has arrived yet |
venue, symbol | the primary engine's mount |
trading_state | Active, Reducing or Halted for the primary engine |
balance | the primary venue's cash balance |
equity_total | cross-venue total equity |
venues | per-venue ledger blocks — balance, realized PnL, fees, funding, equity, unrealized, margin used, free buying power, that venue's own trading_state, and its positions |
orders | the order registry in insertion order, spanning every engine; status is the rendered string |
positions | the primary venue's positions — the documented mirror of venues[0].positions |
held_exits | pending bracket exits held off the venue until their OTO parent fills; empty on non-bracket runs |
recent_events | the bounded delivered-event tail, already rendered to lines |
fault | set once a handler panicked — the core is halted in safe state |
bars | bounded OHLCV tails for the chart |
identity | which daemon, which strategy, paper or live, which build |
bars and identity are #[serde(default)], so a node that predates either field simply omits it.
Two bounds are applied node-side, in crates/vike-tradehub/src/publish.rs's project. bars is
filtered to the primary (venue, symbol) only and tail-sliced to CHART_BARS_CAP closed bars
plus the forming candle — the core's own bar vector is unbounded and would eventually overflow the
framer. recent_events is the core's own bounded ring.
How fresh it is, and what it is not
The observe connection is opened lazily on the first call and then kept open — it stays
subscribed so the node keeps pushing frames, which is why the MCP server holds persistent
connections rather than dialling per call. A freshly-subscribed connection answers with
WireSnapshot::empty (seq: 0) until the first frame lands, so tool_node_snapshot polls every
50 ms for up to about two seconds for a real one before returning.
The observer is lossy by contract
You get the latest published state, never every intermediate one. The node's publisher samples the
core's arc-swap cell on a POLL_INTERVAL of 15 ms against a core that publishes coalesced at 16 ms
or slower, and each subscriber's mailbox is a bounded MAILBOX_CAP drop-oldest queue, so a slow
reader loses old frames rather than back-pressuring the publisher
(crates/vike-tradehub/src/publish.rs). Never treat two consecutive snapshots as a complete event
history — recent_events is a tail, not a ledger.
Nothing here touches the core's hot fold. The publisher's only interaction with the core is one atomic load of the already-published snapshot, on its own thread; reading a snapshot cannot slow the node down.
Its other job: settling an ambiguous write
Every write tool points here when it cannot answer. Server::execute waits ACK_WAIT — two
seconds — for the node's verdict on the command it just sent, and two of the four outcomes are
genuinely unknown rather than failed: a timeout returns outcome: "unknown", and a dropped control
connection returns an error saying the outcome is unknown and the command may have executed. Both
say the same thing: call node_snapshot before retrying, because retrying blind is how one
intent becomes two orders. Acceptance is the same story from the other side — it means the node took
the command, not that the order filled.
When it errors
Both failures are clean tool errors, never panics.
- No node configured —
ensure_observereturnsno vike-tradehub node configured — pass --nodebefore any network work happens;node_snapshot_without_node_is_a_clean_errorpins it. - No observe key — the error names both places the key is looked for, the process environment
and the credential store, and points at
vike-cli secrets pathandvike-cli secrets list. The message says both because it once said only "not set in the environment" while the daemon's own keys sat unread in the store.
A non-null fault is not a tool error but is the most important thing in the payload: the core
entered safe state, which halts every engine and sweeps its working orders
(crates/vike-core/src/runtime/watchdog.rs's enter_safe_state).
Concepts: Portfolio and snapshot,
The node protocol. Tools:
set_trading_state,
market_exit.
Show me the node snapshot — what is open, where is the equity, and is anything faulted?
set_trading_state
The account kill switch as an MCP tool — three states, applied to every engine the node runs, gated like every other write on confirm plus the single-use preview_token, and unable to arm a venue.
Authoring
4 offline tools — compile a Rhai strategy, list its param() knobs, read the starter templates, and see which indicators the script host actually binds.