Skip to content

Backend MCP surface

VerifiedOwner Backend / platformLast verified backend@efa38e04e2db37a444e9f17100323c2ad4b83647 nautilus@2.0.0rc3

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_back fields are integer-typed and range-checked against the same ceilings the gateway itself enforces (e.g. count_back capped 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 /v2 surface (see runtime/index.md for 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 /v2 runtime readiness for the equivalent governed operation — read availability from capability-status.md and the per-node table in trading/portfolio-risk.md, not from a tool's mere existence.
  • A handful of /v2 routes 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.py
  • backend@efa38e04:python/src/onix_engine/mcp/server.py
  • backend@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.