The agent door
What vike-cli mcp is as a CLI verb — a local stdio server, one vocabulary shared with the REPL, a preview gate that fails closed, a profile that can withhold the write tools outright — and where the tool-by-tool reference lives.
vike-cli mcp is the tenth verb and the odd one out. Every other verb answers a question and exits;
this one stays running and serves the platform as Model Context
Protocol tools over stdio, so an agent can drive the same loop
you would drive by hand.
This page is a door, not a duplicate. The tool-by-tool reference — every schema, a runnable call and a captured response — lives under the Trader MCP server, and this page deliberately restates none of it. What belongs here is the part that is a property of the CLI: where the verb sits among the other nine, what it shares with the REPL, and what it refuses to do.
The Trader MCP server
The 17 tools, what each one is for, and what the server is.
The tool reference
One page per tool — schema, a real call, the response it returns.
Client setup
The config block your MCP client needs, per client.
Connect an agent
The narrative walkthrough, end to end.
It is a local subprocess, not a service
usage: vike-cli mcp [--addr 127.0.0.1:7878] [--node <host:port>] \
[--profile full|read-only|offline] [--deny-tool NAME]... \
[--trace | --trace-dir <dir>]Your MCP client launches this binary as a child process and talks to it over stdin and stdout.
Nothing is listening on a port, nothing is published, and nothing contacts a Vike server — the two
addresses are your vike-datahub and your
vike-tradehub. Omit --node and the create-and-
backtest tools still work; the live tools then return a clean error instead.
Something other than an MCP client can be the thing that launches it, and that is how an unattended
session works: vike-agent-run starts this same server against your node, hands one prompt to one
model, and leaves a record — read-only unless it is told otherwise, and scheduled by your operating
system rather than by any mode of this verb. It is a separate binary precisely so that mcp stays
a subprocess and never grows a scheduler. See Unattended runs.
The transport is hand-rolled over serde_json: JSON-RPC 2.0 framed as newline-delimited JSON, one
message per line. That is the same "no async, no new transport stack, stdio-JSON" convention the
rest of this workspace's sidecars use, and it is why an MCP SDK is not a dependency here — it would
drag a second async stack into a binary whose whole identity is being light.
This is the reason every vike-cli verb writes diagnostics to stderr
Under mcp, stdout is a protocol. One stray line on it corrupts the JSON-RPC stream and the
session with it. So the rule is enforced across the whole binary rather than in this one file: the
settings loader returns its resolutions as data instead of logging them, the dispatcher prints them
to stderr, and a rejected user indicator is reported to stderr too. A library that wrote to its
caller's stdout could not be linked here at all.
One vocabulary, two surfaces
Ten of the seventeen tools are read-only — compile a Rhai strategy, discover its knobs, list the indicators the host binds, run a backtest, a sweep or a walk-forward against a data service, list what that service holds, and read a live node's snapshot. Seven write to a node: six place, change or cancel orders and the seventh sets its trading state.
Those seven resolve through the same construction site as the trade REPL:
the same args-to-command mapping, the same client-order-id mint, the same client-side guardrail
(crates/vike-cli/src/cmd/verbs.rs). That is structural rather than a convention. Both surfaces
used to build wire commands independently and had already drifted — the REPL supported modify and
mass-cancel while the agent surface did not, and each carried its own copy of the guardrail. A new
write verb is now visibly missing from whichever surface forgets to expose it, instead of silently
absent from one.
The same is true of the read side: list_indicators and the human
indicators verb print from the same derivation, so the human
surface and the machine surface cannot disagree about what a script may call — which is the only
property that matters there, because the cost of being wrong is a strategy that mounts and silently
never trades.
The preview gate fails closed
Every write tool previews. Calling one returns the resolved command, a guardrail check, the node's
own dry-run verdict where the node can be reached, and a preview_token — and sends nothing.
Executing needs a second call carrying confirm: true and that exact token. The token fires at
most once, expires after sixty seconds, and is bound to the command it previewed. Any other
shape — no token, an unknown token, an expired one, a token issued for a different command, or
confirm: true on its own — returns a preview and sends nothing.
That gate was documented before it existed
confirm: true used to be the whole test, on that call, so a first-and-only call executed. Nothing
correlated a confirm with the preview it claimed to confirm, so previewing qty: 0.5 and confirming
qty: 50 was accepted. And the client-side guardrail it showed cannot price a market order —
there is no price to size against — which is the default order type, so the advertised protection
was absent on exactly the path most likely to be taken. The preview now also asks the node for its
own dry-run, and when the node cannot be reached it says the verdict is an unverified
client-side estimate rather than presenting an absence as approval.
The node's server-side control limits and the core risk gate remain the enforcing backstop regardless. The preview is UX; the node gate is truth. See the control boundary.
…and a scope that removes the write tools entirely
The gate above is what a write costs. --profile is the prior question — whether this server offers
one at all — and it is a property of the launch command rather than of the conversation. read-only
serves the ten read tools and withholds the seven writes; offline serves only the four that open
no socket. --deny-tool NAME takes out one more, repeatably. A withheld tool is absent from
tools/list as well as refused, because the advertised roster and the routing gate are two
complements of one pass over the registry (ToolAccess in crates/vike-cli/src/cmd/mcp.rs) rather
than two lists that could disagree. An unknown profile or deny name is a usage error naming the
valid set — never a quiet widening.
--trace is the other half of handing the tools out: one appended JSONL record per tool call under
<project>/settings/state/agent, carrying the tool, the redacted arguments and the verdict —
including the refusals, which reach no node audit trail because they were refused before a byte
left this process. Off unless asked for, and it records no secret and no token bytes; the
verb reference has the shape.
Keys, and what an agent can reach
The live tools need the same two HMAC keys everything else does, resolved the same way — process environment first, then the credential store the daemon itself reads. Absent from both, the trade tools return a clean error naming both places and the create-and-backtest tools keep working, which is the right degrade: an agent that cannot reach a node can still author and backtest.
What an agent may arm is not an agent question at all. policy.venues.<venue> is a per-venue
ceiling consulted above the credential read, defaulting to paper for every venue — so a box with
no [venues] table mounts all paper whatever its store holds, and no tool call changes that. See
policy.toml and
risk and arming.
The daemon and the app
Three clients of one trading node — the desktop app in observe mode, the CLI's REPL and read verb, and the MCP server — what each is best at, and what strategy-status actually reads.
Scripting it
The machine-facing half — which verbs have --json and what each one emits, the two exit codes, the stdout/stderr split, and how to branch on all of it in a shell script.