Backend MCP surface¶
ONIX's backend ships its own MCP server (onix_engine.mcp) exposing the same governed /v2
service that the gateway serves over HTTP — there is no separate engine behind it, and no MCP tool
does anything a REST/SSE route could not also do. This page documents that backend surface. It is
a different thing from the native/local MCP host, documented at ../native/index.md.
Transport: stdio only¶
At this pin, onix_engine.mcp.server wires the tool table to a stdio-only MCP server: the
module never constructs an HTTP/SSE transport (no streamable_http_app()/uvicorn call
anywhere in the file), and its own docstring states this directly. The mcp SDK dependency
transitively pulls in starlette/uvicorn/sse-starlette because that package serves every MCP
transport from one distribution, but nothing in this codebase imports or runs the HTTP surface.
This is not an auth-gated network service — there is no network listener to gate.
Shape¶
At this pin, the tool table is frozen and enumerable, verified directly by parsing the two tuples the module defines (not by counting substring matches in the source text, which over-counts):
| Set | Count | Mutating (read_only=False) |
|---|---|---|
TOOLS (the default advertised set) |
81 | 16 |
GATED_TOOLS (one tool: ibkr_cancel_order) |
1 | 1 |
| Maximum, both sets combined | 82 | 17 |
GATED_TOOLS is registered only when the process is started with ONIX_MCP_ALLOW_CONTROL=1
(default off) — the default deployment advertises exactly TOOLS (81 tools, 16 mutating). A
separate --tool-profile operator-control flag narrows advertisement to the frozen
OPERATOR_CONTROL_TOOL_NAMES subset for the private integration operator gateway, and is mutually
exclusive with allow_control; the default profile stays exactly TOOLS. The default set is
therefore not wholly read-only: sixteen TOOLS entries mutate spine runtime state
(spine_sandbox_stop, the Strategy Studio authoring/submission set, and optimization-study
control) and are marked read_only=False on their ToolSpec.
Most tools are thin projections of one gateway route each (list instruments, query bars, read status/account/portfolio/orders/fills/positions, read coverage accounting, submit/read a backtest, start/stop/read the sandbox runtime); a handful cover Strategy Studio authoring (draft file read/write, revision save, validation) and optimization-study control.
The sandbox-lifecycle tool specs exist; the operation they call does not, at this pin.
sandbox_start_revision, sandbox_stop/spine_sandbox_stop, and sandbox_status are real,
enumerable ToolSpecs that call the corresponding /v2/sandbox/* gateway route exactly as
documented. On the core spine and Binance product node, that route itself raises a typed deferred
refusal (CAPABILITY_DEFERRED, HTTP 501) — see trading/execution-runtime.md
for the full account of why. Calling the tool therefore returns that refusal, not a sandbox run.
This page separates three different claims on purpose: the tool spec exists (verified), the
tool is advertised/activated by default (verified), and the underlying capability is
available (it is not, on the nodes that defer it — see
capability-status.md).
Input validation: bounded types, not closed enums¶
Every tool argument is validated before a single byte reaches the underlying gateway client, stated as an explicit security requirement in source: "a caller must not be able to name a URL." No tool accepts a raw URL, a filesystem path, an HTTP header, or a raw query dictionary, and no gateway path a handler requests is caller-supplied — it is a literal chosen by that handler's own code. Concretely:
- Instrument/strategy/job ids match a fixed pattern (
[A-Za-z0-9_.\-]{1,128}) — no/, no whitespace, no control characters, so an id can never smuggle a path segment. - Nanosecond timestamps must be digit-only, no leading zero (except
"0"itself), length-bounded. limit/cursor/count_backfields are integer-typed and range-checked against the same ceilings the gateway itself enforces (e.g.count_backcapped at 5,000 — the durable catalog's own per-series rolling cap, so a request can never usefully ask for more than the catalog could ever hold).- Every tool's argument set is a closed set of keys: an unexpected key is rejected, never silently ignored.
The one field this is not true of: config. The config input on the backtest-submission and
sandbox-start tools is an arbitrary, nested Nautilus strategy configuration object — by design,
because Nautilus's own StrategyConfig shapes are open-ended and this seam must accept whatever a
sealed revision's own config schema requires. _validate_config_field bounds it two ways instead
of by enum: it must be a JSON object, and it must serialize to at most 65,536 bytes
(_MAX_CONFIG_BYTES). This is a type-and-size ceiling, not a closed-value check, and it is the one
documented exception to "closed enum or bounded scalar" above.
Every tool call returns a closed, typed outcome envelope. The MCP module itself has no opinion on MCP wire framing — its only concern is what a gateway call is allowed to look like and what its result means.
Mutating tools¶
Mutation is judged by effect, not HTTP verb — a bounded POST query (submitting a screener screen
definition, for instance) is still classified read-only, because it evaluates a document and
changes nothing. The sixteen TOOLS entries marked mutating are: spine_sandbox_stop,
strategy_create, strategy_file_put, strategy_validate, revision_save, backtest_run,
backtest_cancel, sandbox_start_revision, sandbox_stop, spine_create_study,
spine_pause_study, spine_resume_study, spine_cancel_study, spine_retry_study_trial,
spine_promote_study_trial, and spine_evaluate_study_holdout. The gated seventeenth
(ibkr_cancel_order) is the only tool in either set with origin="ibkr". None of these tools
places a real venue order — see trading/execution-runtime.md for
why no execution client exists for a mutating tool to route an order through in the first place. A
stale wire form that once let a caller submit an arbitrary importable strategy path directly
(bypassing the sealed-revision requirement) has been removed from both the REST and MCP surfaces;
the only strategy-execution entry point today is a sealed {strategy_id, revision_id} pair.
Discoverability without duplication¶
MCP tool coverage is checked against the gateway's own live route tables by an automated test
(python/tests/test_capability_registry.py), and the two known, honestly-documented gaps are
stated rather than hidden:
- Fifteen tools still front the older v1 fixture/runtime gateway rather than the governed
/v2surface (seeruntime/index.mdfor that distinction), and are out of scope for the capability registry by construction:get_liveness,get_readiness,get_engine_status,get_venue_capability_matrix,get_run_receipt,list_instruments,get_backtest_result,list_bars,list_signals,list_retirements,list_screener_rows,list_chart_markers,get_catalog_summary,list_schemas,get_schema. Route/schema publication for these does not imply/v2runtime readiness for the equivalent governed operation — read availability fromcapability-status.mdand the per-node table intrading/portfolio-risk.md, not from a tool's mere existence. - A handful of
/v2routes have no MCP tool yet (health, screener snapshot, one coverage variant, order submission — only order cancellation is currently exposed over MCP, and two selection-management routes).
Both gaps are tracked by name in source (onix_engine.capability_registry), not left implicit.
Evidence and source pins for this page
Verified. Current behaviour, confirmed in source at the pinned commit.
Verified on against the following immutable sources:
backend@efa38e04:python/src/onix_engine/mcp/tools.pybackend@efa38e04:python/src/onix_engine/mcp/server.pybackend@efa38e04:docs/contracts/market-spine-v2.md
Status tokens are defined on the documentation and status model page. Every pin on this site is listed under versions and source pins.