vike

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.

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.

On this page