Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Documentation

This directory contains developer-facing usage, production, and operations documentation.

Start Here

  • Developer Guide: install, CLI, SDK, MCP, order workflows, audit, and validation commands.
  • Production Readiness: deployment checklist, secrets, remote MCP, audit, sidecar, paper, and live gates.
  • Public API: supported Rust facade modules and examples.
  • Testing: local/CI gates, fixture coverage, replay, secret regression, and performance budgets.

Feature Guides

Developer Guide

This guide is the shortest path for developers who want to use or extend ibkr-agent-gateway.

Install and Run

From this checkout:

cargo run --bin ibkr-agent -- health --json
cargo run --bin ibkr-agent -- accounts list --json

Install the CLI locally:

cargo install --path .
ibkr-agent health --json

Embed the library from another local Rust project:

cargo add ibkr-agent-gateway --path /path/to/ibkr-agent-gateway
use ibkr_agent_gateway::prelude::*;

#[tokio::main]
async fn main() -> Result<(), GatewayError> {
    let gateway = Gateway::new(GatewayConfig::fake_local())?;
    let accounts = gateway.list_accounts().await?;
    println!("visible_accounts={}", accounts.len());
    Ok(())
}

Backend Modes

Use GatewayConfig::fake_local() while developing. It reads deterministic fixtures from tests/fixtures/cpapi/ and needs no broker session.

Use GatewayConfig::client_portal(url) only when the Interactive Brokers Client Portal Gateway is already running and manually authenticated outside this project. TLS verification can be disabled only for localhost URLs.

For production-like deployments, read production-readiness.md. The CLI runner defaults to fake fixtures only when --config is omitted. A real broker CLI run should pass an explicit YAML config, such as config/local.example.yaml, and supply the configured audit HMAC secret through the environment.

CLI Surface

Read and session inspection:

ibkr-agent backend status --json
ibkr-agent session requirements --json
ibkr-agent account summary --account DU1234567 --json
ibkr-agent portfolio snapshot --account DU1234567 --json
ibkr-agent positions list --account DU1234567 --json
ibkr-agent market snapshot --contract-id 265598 --json
ibkr-agent executions list --account DU1234567 --json

Order preview is non-executable and must be explicitly enabled:

ibkr-agent orders preview \
  --account DU1234567 \
  --symbol AAPL \
  --side buy \
  --quantity 1 \
  --limit-price 100 \
  --currency USD \
  --enable-preview \
  --json

Paper submit/cancel commands require explicit paper enablement and an idempotency key. Submit also requires the approval id returned by approvals create:

ibkr-agent approvals create --account DU1234567 --preview-id <preview_id> --ttl-seconds 300 --json
ibkr-agent orders submit --account DU1234567 --approval-id <approval_id> --idempotency-key paper-submit-001 --enable-paper --json
ibkr-agent orders cancel --account DU1234567 --broker-order-id paper-order-local --idempotency-key paper-cancel-001 --enable-paper --json

CLI live submit and cancel run the full gate stack and then call a LiveOrderWriter. The CLI defaults to LocalCandidateLiveWriter, so the broker order id is a deterministic local-candidate-* value. Use --live-broker client-portal with a Client Portal Gateway config to call ClientPortalLiveWriter from the CLI; --live-broker refusing is available for fail-closed checks. Production deployments can also inject their own writer through the SDK boundary (see docs/production-readiness.md):

ibkr-agent orders live-submit \
  --account DU1234567 \
  --approval-id <approval_id> \
  --idempotency-key live-submit-001 \
  --enable-live \
  --live-scope \
  --open-kill-switch \
  --acknowledge-paper-to-live \
  --live-broker local-candidate \
  --json

Audit review:

ibkr-agent audit tail --limit 20 --json
ibkr-agent audit export --limit 500 --json
ibkr-agent audit verify --json

MCP

Local stdio MCP:

ibkr-agent mcp serve --transport stdio --describe --json
ibkr-agent mcp serve --transport stdio --json

--describe exits after a smoke-check description. Without it, the command runs the stdio JSON-RPC loop, advertises only tools enabled by local scopes, and audits every tool call.

Remote HTTP MCP is disabled by default. Enabling it requires complete remote MCP runtime config, an OAuth/OIDC issuer, RS256/RSA JWKS validation, a token-id HMAC secret, accepted audiences, allowed scopes, and the independent safety flag. In CLI YAML, that safety flag is safety.remote_mcp_enabled; the SDK-facing GatewayConfiguration field is safety.remote_public_mcp_enabled.

ibkr-agent --config config/remote.example.yaml mcp serve --transport http --enable-remote-mcp --bind 127.0.0.1:8080

The HTTP listener serves protected-resource metadata and routes authorized JSON-RPC requests on POST /mcp. See remote-mcp-oauth.md.

Example client configs live under examples/mcp-clients/.

Validation

Run these before changing package behavior:

cargo fmt --check
cargo clippy --workspace --all-targets --features unstable-internal-test-support -- -D warnings
cargo test --workspace --features unstable-internal-test-support
cargo test --workspace --features unstable-internal-test-support secret

Run packaging checks before release work:

cargo package --allow-dirty --no-verify --list
cargo publish --dry-run --locked

Safety Rules for Contributors

  • Do not return broker cookies, bearer tokens, credentials, raw headers, local paths, or raw session material from any CLI, MCP, log, fixture, or audit path.
  • Keep broker logic provider-neutral; provider-specific behavior belongs in compatibility examples and tests.
  • Keep write-capable flows fail-closed unless explicit config, scope, approval, idempotency, audit, and risk gates are satisfied.
  • Preserve the public facade boundary in src/public/* and src/lib.rs; keep implementation details under src/internal/*.

Getting Started Locally

This page is kept for the original MVP path. For the complete current package guide, start with developer-guide.md.

Local Fake Backend

The fastest development path uses fake Client Portal Gateway fixtures:

cargo run --bin ibkr-agent -- health --json
cargo run --bin ibkr-agent -- backend status --json
cargo run --bin ibkr-agent -- session requirements --json
cargo run --bin ibkr-agent -- accounts list --json

When no --config path is provided, the CLI uses fixtures under tests/fixtures/cpapi/ and stores local audit/workflow state in a SQLite file under the system temp directory.

Read Commands

ibkr-agent account summary --account DU1234567 --json
ibkr-agent portfolio snapshot --account DU1234567 --json
ibkr-agent positions list --account DU1234567 --json
ibkr-agent contracts search AAPL --asset-class stock --currency USD --exchange SMART --json
ibkr-agent contracts resolve AAPL --asset-class stock --currency USD --exchange SMART --json
ibkr-agent market snapshot --contract-id 265598 --json
ibkr-agent market bars --contract-id 265598 --duration "1 D" --bar-size "5 mins" --json
ibkr-agent orders list --account DU1234567 --json
ibkr-agent orders status --account DU1234567 --broker-order-id 123 --json
ibkr-agent executions list --account DU1234567 --json

Feature-Gated Workflows

Order preview, paper order lifecycle, remote MCP, sidecar relay, provider compatibility, and live safety gates now exist in the package. They remain fail-closed and require explicit flags or configuration.

Use:

Validation

cargo fmt --check
cargo clippy --workspace --all-targets --features unstable-internal-test-support -- -D warnings
cargo test --workspace --features unstable-internal-test-support

Interactive Brokers Client Portal Gateway

Retail Client Portal Gateway authentication is handled outside this project by the local Interactive Brokers gateway process.

The local gateway reports whether the broker session is usable, requires manual action, expired, or unavailable. It must not return broker cookies, credentials, raw headers, local secret paths, or raw session material to CLI, MCP, logs, or audit records.

Session States

Session and keepalive outcomes map into safe statuses:

  • usable: read-only account discovery may proceed.
  • manual_action_required: the user must complete or refresh broker login outside this gateway.
  • unavailable: the local Client Portal Gateway cannot be reached.

The fake backend covers connected, missing-session, expired-session, keepalive-success, and keepalive-expired fixtures so session behavior can be validated offline before any live broker session is used.

Contextual Read Endpoints

The contextual read adapter includes fixture-backed and wiremock-covered paths for options, greeks, market depth, scanners, news, fundamentals, calendar/session, FX, and transfer history. These tests lock the gateway client contract only. Interactive Brokers can vary Client Portal Gateway endpoint availability and naming by Gateway build and entitlement, so validate every contextual read against the exact deployed IBKR Gateway before relying on it in production.

Troubleshooting

  • If backend status reports manual action required, complete or refresh the broker login in the Interactive Brokers Client Portal Gateway UI.
  • If keepalive fails, broker-backed read tools must be treated as unavailable until manual login is restored.
  • If the local gateway is unreachable, check that the Client Portal Gateway process is running and that the configured URL is local.
  • verify_tls=false is only valid for the exact hosts localhost, 127.0.0.1, or ::1; wider loopback ranges and IPv4-mapped IPv6 addresses are intentionally not treated as local by the config validator.
  • Broker cookies, raw headers, tokens, credential file paths, and raw session material must never appear in CLI, MCP, logs, fixtures, or audit output.

For offline checks, run the fake fixture test suite with:

cargo test --workspace --features unstable-internal-test-support

CLI and Tool Commands

This document lists the developer-facing CLI flows. Without --config, the CLI uses fake fixtures and a local SQLite audit/state file under the system temp directory. With --config, a missing or invalid file fails closed.

Health, Session, and Accounts

ibkr-agent health --json
ibkr-agent backend status --json
ibkr-agent session requirements --json
ibkr-agent accounts list --json

Session and backend outputs must not include cookies, bearer tokens, raw headers, local secret paths, or raw broker session material.

Portfolio, Positions, Contracts, and Market Data

ibkr-agent account summary --account DU1234567 --json
ibkr-agent portfolio snapshot --account DU1234567 --json
ibkr-agent positions list --account DU1234567 --json
ibkr-agent contracts search AAPL --asset-class stock --currency USD --exchange SMART --json
ibkr-agent contracts resolve AAPL --asset-class stock --currency USD --exchange SMART --json
ibkr-agent market snapshot --contract-id 265598 --json
ibkr-agent market bars --contract-id 265598 --duration "1 D" --bar-size "5 mins" --json

Account-scoped commands require an explicit account id. Contract resolution fails closed when matches are ambiguous. Market data carries status so delayed, stale, or unavailable data can be labeled or refused by policy.

Read-Only Orders and Executions

ibkr-agent orders list --account DU1234567 --json
ibkr-agent orders status --account DU1234567 --broker-order-id 123 --json
ibkr-agent executions list --account DU1234567 --json

These commands inspect existing broker records only.

MCP-Only Maturity Reads

The MCP registry also exposes consultative and safety read tools when their scopes are present:

ToolScope
ibkr_pnl_dailyibkr:portfolio:read
ibkr_pnl_realtimeibkr:portfolio:read
ibkr_orders_historyibkr:orders:read
ibkr_account_metadataibkr:accounts:read
ibkr_kill_switch_statusibkr:health:read
ibkr_limits_statusibkr:risk:read
ibkr_audit_exportibkr:audit:export
ibkr_session_renewibkr:health:read

These tools are read-only or operational visibility tools. They do not submit, cancel, modify, or approve broker orders.

Advanced market research tools are also MCP-only in this maturity increment:

ToolScope
ibkr_options_chainibkr:options:read
ibkr_option_greeksibkr:options:read
ibkr_market_depthibkr:marketdata:depth:read
ibkr_scanner_runibkr:scanner:read

Contextual account and market tools are MCP-only and read-only:

ToolScope
ibkr_news_listibkr:news:read
ibkr_news_articleibkr:news:read
ibkr_fundamentals_getibkr:fundamentals:read
ibkr_market_sessionibkr:calendar:read
ibkr_market_holidaysibkr:calendar:read
ibkr_currency_rateibkr:currency:read
ibkr_transfer_historyibkr:transfers:read

Order Preview

Preview is non-executable and disabled unless explicitly enabled:

ibkr-agent orders preview \
  --account DU1234567 \
  --symbol AAPL \
  --side buy \
  --quantity 1 \
  --limit-price 100 \
  --currency USD \
  --enable-preview \
  --json

Without --enable-preview, the command returns ORDER_PREVIEW_DISABLED. MCP preview additionally accepts stop, stop-limit, and trailing-stop candidates with the required price or trailing fields for each order type. Market candidates remain refused by policy.

Paper Orders

ibkr-agent approvals create --account DU1234567 --preview-id <preview_id> --ttl-seconds 300 --json
ibkr-agent orders submit --account DU1234567 --approval-id <approval_id> --idempotency-key paper-submit-001 --enable-paper --json
ibkr-agent orders cancel --account DU1234567 --broker-order-id paper-order-local --idempotency-key paper-cancel-001 --enable-paper --json

Paper submit requires an approval id returned by approvals create for the specific preview id. Paper submit/cancel/modify require explicit paper enablement and an idempotency key. Reusing the same key with different canonical request inputs is refused.

MCP also exposes ibkr_approvals_create under ibkr:approvals:create. It creates a gateway approval record only for an existing, unexpired order preview stored by the server and does not represent a broker-side write.

Live-Gated Candidates

ibkr-agent orders live-submit \
  --account DU1234567 \
  --approval-id <approval_id> \
  --idempotency-key live-submit-001 \
  --enable-live \
  --live-scope \
  --open-kill-switch \
  --acknowledge-paper-to-live \
  --live-broker local-candidate \
  --json
ibkr-agent orders live-cancel \
  --account DU1234567 \
  --broker-order-id local-candidate-live-submit-001 \
  --idempotency-key live-cancel-001 \
  --enable-live \
  --live-scope \
  --open-kill-switch \
  --acknowledge-paper-to-live \
  --live-broker local-candidate \
  --json

Live submit, cancel, and modify run the full gate stack and then call a LiveOrderWriter. --live-broker selects local-candidate, client-portal, or refusing; the default returns deterministic local-candidate-* ids. client-portal requires a config using broker.backend: client_portal_gateway.

When --config is supplied, live commands use the validated live_trading.allowed_accounts and live_trading.risk_policy_id from that runtime config. The CLI invocation flags (--enable-live, --live-scope, --open-kill-switch, and --acknowledge-paper-to-live) do not add the target account to the allowlist and cannot replace the configured live policy.

Live submit rate counters and session notional are derived from durable audit workflow state before the gate stack runs. Caller-supplied submitted_in_window, submitted_in_session, and instrument context are not trusted by CLI or MCP live paths.

MCP live modify follows the same server-loaded pattern. The client supplies approval_id and preview_id for the replacement preview plus bounded changes; the server reloads the approved order, applies live limits, and consumes the approval only after a successful writer result.

Bracket Orders

MCP exposes explicit bracket tools for grouped entry, take-profit, and stop-loss workflows: ibkr_bracket_order_preview, ibkr_paper_bracket_order_submit, and ibkr_live_bracket_order_submit. See bracket-orders.md. OCA is not exposed as an MCP tool in this release.

Audit and MCP

ibkr-agent audit tail --limit 20 --json
ibkr-agent audit export --limit 500 --json
ibkr-agent audit verify --json
ibkr-agent mcp serve --transport stdio --describe --json
ibkr-agent mcp serve --transport stdio
ibkr-agent mcp serve --transport http --describe --enable-remote-mcp --json
ibkr-agent --config config/remote.example.yaml mcp serve --transport http --enable-remote-mcp --bind 127.0.0.1:8080

--describe is a smoke check that prints the selected transport description and exits. Without --describe, the stdio command runs the local MCP JSON-RPC loop. It advertises only tools whose scopes are enabled by the current CLI config and audits every tools/call.

Without --describe, the HTTP command binds the selected address, validates OAuth/OIDC bearer tokens, serves /.well-known/oauth-protected-resource, and routes JSON-RPC requests on POST /mcp through the same scoped tool handlers.

CLI audit commands require ibkr:audit:read in the current local scope set and write their own redacted audit event after tail/export/verify completes.

Remote HTTP MCP and sidecar flows are disabled by default and fail closed without explicit config or flags. See remote-mcp-oauth.md and sidecar-relay.md.

Sidecar Relay

ibkr-agent sidecar identity create --public-key <public-key> --display-name laptop --json
ibkr-agent sidecar pairing create --remote-instance-id remote-1 --sidecar-id sidecar-example --user-id local-user --ttl-seconds 300 --json
ibkr-agent sidecar session create --remote-instance-id remote-1 --sidecar-id sidecar-example --ttl-seconds 300 --json
ibkr-agent sidecar relay accept --remote-instance-id remote-1 --sidecar-id sidecar-example --tool-name ibkr_accounts_list --scope ibkr:accounts:read --payload-json '{}' --json

The sidecar commands expose the relay primitives: identity, pairing, session creation, and sensitive-payload rejection for forwarded requests. They are not a long-running daemon and do not automate Client Portal Gateway login.

MCP

The package exposes provider-neutral MCP tooling for local stdio clients and remote HTTP clients.

Broker authentication remains separate from MCP authorization. MCP bearer tokens must never be forwarded to IBKR.

Local Stdio

ibkr-agent mcp serve --transport stdio --describe --json
ibkr-agent mcp serve --transport stdio --json

Use --describe for a smoke check that exits immediately. Omit it when wiring an MCP client; the command then runs a line-oriented JSON-RPC stdio loop.

The local server lists only tools whose scopes are enabled by the current CLI config. Every tools/call enforces the tool scope before backend access and writes an audit event for completion, denial, refusal, or failure.

Example client configs live under examples/mcp-clients/.

Remote HTTP

Remote MCP is disabled by default and requires explicit configuration plus the independent safety flag. See remote-mcp-oauth.md.

Use --describe for a config smoke check, or omit it to bind the HTTP listener and serve JSON-RPC requests on POST /mcp:

ibkr-agent mcp serve --transport http --describe --enable-remote-mcp --json
ibkr-agent --config config/remote.example.yaml mcp serve --transport http --enable-remote-mcp --bind 127.0.0.1:8080

The HTTP transport validates OAuth/OIDC bearer tokens, serves protected-resource metadata, filters tool discovery by granted scopes, and routes authorized tools/call requests through the same handlers as stdio.

Tool Registry

Read tools:

ToolScope
ibkr_healthibkr:health:read
ibkr_backend_statusibkr:health:read
ibkr_session_requirementsibkr:health:read
ibkr_session_renewibkr:health:read
ibkr_kill_switch_statusibkr:health:read
ibkr_accounts_listibkr:accounts:read
ibkr_account_metadataibkr:accounts:read
ibkr_account_summaryibkr:portfolio:read
ibkr_pnl_dailyibkr:portfolio:read
ibkr_pnl_realtimeibkr:portfolio:read
ibkr_positions_listibkr:positions:read
ibkr_portfolio_snapshotibkr:portfolio:read
ibkr_contracts_searchibkr:marketdata:read
ibkr_contract_resolveibkr:marketdata:read
ibkr_market_snapshotibkr:marketdata:read
ibkr_historical_barsibkr:marketdata:read
ibkr_options_chainibkr:options:read
ibkr_option_greeksibkr:options:read
ibkr_market_depthibkr:marketdata:depth:read
ibkr_scanner_runibkr:scanner:read
ibkr_news_listibkr:news:read
ibkr_news_articleibkr:news:read
ibkr_fundamentals_getibkr:fundamentals:read
ibkr_market_sessionibkr:calendar:read
ibkr_market_holidaysibkr:calendar:read
ibkr_currency_rateibkr:currency:read
ibkr_transfer_historyibkr:transfers:read
ibkr_orders_listibkr:orders:read
ibkr_orders_historyibkr:orders:read
ibkr_order_statusibkr:orders:read
ibkr_executions_listibkr:orders:read
ibkr_limits_statusibkr:risk:read
ibkr_audit_tailibkr:audit:read
ibkr_audit_exportibkr:audit:export

Preview and paper tools are discoverable when their scopes are enabled:

ToolScope
ibkr_order_previewibkr:orders:preview
ibkr_bracket_order_previewibkr:orders:preview
ibkr_paper_order_submitibkr:orders:paper:submit
ibkr_paper_order_cancelibkr:orders:paper:cancel
ibkr_paper_order_modifyibkr:orders:paper:modify
ibkr_paper_bracket_order_submitibkr:orders:paper:submit
ibkr_approvals_createibkr:approvals:create

Live tools are discoverable when live scopes are enabled:

ToolScope
ibkr_live_order_submitibkr:orders:live:submit
ibkr_live_order_cancelibkr:orders:live:cancel
ibkr_live_order_modifyibkr:orders:live:modify
ibkr_live_bracket_order_submitibkr:orders:live:submit

Live submit arguments are account_id, approval_id, preview_id, and idempotency_key. Live modify arguments are account_id, broker_order_id, approval_id, preview_id, idempotency_key, and at least one bounded change. The handlers load approval, preview, live policy, writer, market snapshot, and audit state from the server runtime; these values are not trusted from the MCP payload. Successful submits are added to the live reconciliation backlog. Cancel and modify results preserve the broker status, and only terminal states are removed from pending reconciliation.

Bracket submit tools require three approvals that belong to the same server-persisted bracket preview. Mixed approvals from different preview groups are refused before any paper or live writer boundary. Paper bracket submit also persists durable idempotency state and consumes the three approvals after a successful grouped submit.

The first specs/009-mcp-tool-maturity/ additions are consultative and safety read tools: PnL, order history, account metadata, kill switch status, live limits status, MCP audit export, and explicit session renewal. Write-capable modify additions are explicit (ibkr_paper_order_modify and ibkr_live_order_modify), while the generic ibkr_order_modify name remains forbidden.

The same maturity spec adds contextual reads for news, fundamentals, market session/holiday data, FX rates, and transfer history. ibkr_approvals_create is a gateway workflow write, not a broker write: it requires ibkr:approvals:create and an existing unexpired preview persisted by the server.

Forbidden Generic Write Tools

These generic write-like names remain forbidden:

  • ibkr_order_intent_validate
  • ibkr_order_preview_explain
  • ibkr_order_submit
  • ibkr_order_cancel
  • ibkr_order_modify
  • ibkr_order_approve

Use the explicit preview, paper, or live-gated tools instead. Direct calls to forbidden names return READONLY_WRITE_FORBIDDEN and are auditable.

Safety Boundary

Tool outputs must not include broker cookies, tokens, credentials, sensitive headers, local secret paths, or raw Client Portal Gateway session material. Scope checks happen before broker access. Missing scope returns AUTH_MISSING_SCOPE and does not call the backend.

Remote MCP OAuth/OIDC

Remote MCP is disabled by default. Enabling it requires both the remote MCP configuration block and the explicit safety flag, so an incomplete deployment fails closed instead of exposing unauthenticated broker tools.

The CLI YAML config uses safety.remote_mcp_enabled. The public Rust GatewayConfiguration struct exposes the equivalent SDK-facing field as safety.remote_public_mcp_enabled.

Minimum configuration fields:

  • remote_mcp.enabled: true
  • remote_mcp.resource: public protected-resource identifier for the gateway
  • remote_mcp.issuer: expected OIDC issuer
  • remote_mcp.jwks_url: JWKS endpoint used for token signature checks
  • remote_mcp.audiences: accepted token audiences/resources
  • remote_mcp.allowed_scopes: gateway scopes that may be granted remotely
  • remote_mcp.token_id_hmac_secret_env: environment variable containing the deployment secret used to hash token ids for audit correlation
  • remote_mcp.rate_limit_max_requests: per-client authorization attempts per window
  • remote_mcp.rate_limit_window_seconds: rate-limit window duration
  • remote_mcp.max_connections: concurrent HTTP connections accepted by the transport before returning 503
  • safety.remote_mcp_enabled: true

The CLI HTTP transport is functional when --describe is omitted:

ibkr-agent --config config/remote.example.yaml mcp serve --transport http --enable-remote-mcp --bind 0.0.0.0:8080

It binds the configured address, serves protected-resource metadata at /.well-known/oauth-protected-resource, and accepts JSON-RPC MCP requests at POST /mcp. The implementation intentionally uses a small bounded HTTP/1.1 server: it validates Content-Length, caps headers and bodies, returns one response per connection, caps concurrent connections, applies configured rate limits, and routes authorized requests through the same tool handlers as stdio.

Broker authentication remains separate from MCP client authorization; MCP bearer tokens must never be forwarded to IBKR.

Example configuration shape:

remote_mcp:
  enabled: true
  bind_address: 0.0.0.0:8080
  resource: https://gateway.example.com/mcp
  issuer: https://auth.example.com/
  jwks_url: https://auth.example.com/.well-known/jwks.json
  metadata_url: https://auth.example.com/.well-known/openid-configuration
  audiences:
    - https://gateway.example.com/mcp
  allowed_scopes:
    - ibkr:health:read
    - ibkr:accounts:read
    - ibkr:portfolio:read
    - ibkr:positions:read
    - ibkr:marketdata:read
    - ibkr:orders:read
    - ibkr:audit:read
  clock_skew_seconds: 60
  rate_limit_max_requests: 120
  rate_limit_window_seconds: 60
  max_connections: 64
  token_id_hmac_secret_env: IBKR_REMOTE_TOKEN_HMAC_SECRET

safety:
  remote_mcp_enabled: true

Request behavior:

  • missing, malformed, expired, wrong issuer, wrong audience, or bad signature: 401 with WWW-Authenticate and protected-resource metadata
  • valid token with missing tool scope: 403
  • repeated authorization attempts from the same forwarded IP or MCP session: 429
  • concurrent connections beyond remote_mcp.max_connections: 503
  • valid token with the required scope: the request is authorized and the token is not included in downstream broker calls or audit payloads
  • valid UUID x-request-id and mcp-session-id headers are preserved for request/session correlation; missing or malformed values are replaced with gateway-generated ids
  • initialize and tools/list validate the bearer token without a single required tool scope; visible tools are filtered to the token scopes allowed by remote_mcp.allowed_scopes
  • tools/call requires the called tool scope before any backend, order writer, or audit workflow access

Production builds validate RS256 JWTs against RSA JWKS keys. HS256 and JWKS oct key material are compiled only with the unstable-internal-test-support feature for deterministic local and CI coverage. Adding ES256 provider keys should stay inside the OAuth verifier without changing broker-core crates or tool schemas.

Scopes

Scopes are explicit gateway permissions. Local scopes are loaded from configuration or test harnesses; remote MCP scopes are granted only after OAuth token validation and intersection with remote_mcp.allowed_scopes.

IBKR broker authentication is separate from gateway scopes.

Read Scopes

ScopePurpose
ibkr:health:readhealth, backend status, session requirements
ibkr:accounts:readaccount discovery
ibkr:portfolio:readaccount summary and portfolio snapshot
ibkr:positions:readpositions
ibkr:marketdata:readcontract search/resolve, snapshots, bars
ibkr:orders:readread-only orders and executions
ibkr:audit:readredacted audit tail
ibkr:audit:exportredacted audit export
ibkr:risk:readrisk policy, risk result, and live limit inspection
ibkr:options:readoptions chain and greeks
ibkr:marketdata:depth:readbounded Level II/depth reads
ibkr:scanner:readallowlisted market scanners
ibkr:news:readbounded broker news metadata and articles
ibkr:fundamentals:readbounded fundamentals reports
ibkr:calendar:readholidays and market session status
ibkr:currency:readread-only FX rates
ibkr:transfers:readredacted transfer history

Preview, Paper, Approval, and Live Scopes

ScopePurpose
ibkr:orders:previewnon-executable order preview
ibkr:orders:paper:submitpaper submit lifecycle
ibkr:orders:paper:cancelpaper cancel lifecycle
ibkr:orders:paper:modifypaper order modification lifecycle
ibkr:approvals:createMCP-created gateway approval records for existing previews
ibkr:orders:live:submitlive submit through the live order writer
ibkr:orders:live:cancellive cancel through the live order writer
ibkr:orders:live:modifylive-gated order modification lifecycle

Preview, paper, and live scopes do not bypass feature flags, approvals, idempotency, risk limits, kill switch, audit availability, or migration checklists.

The local scope-set constructors enforce a tier hierarchy:

  • ScopeSet::local_with_preview accepts only read and preview scopes.
  • ScopeSet::local_with_paper accepts read, preview, paper, and approval scopes, but refuses live scopes with AUTH_SCOPE_NOT_ALLOWED_IN_MVP.
  • ScopeSet::local_with_live accepts every local scope, including live ones. Remote OAuth contexts use this constructor to preserve the historical wide remote scope surface.

MCP Tool Mapping

The MCP registry is scope-filtered. Local stdio discovery uses the local scope set; remote HTTP discovery uses the validated bearer-token scopes after intersection with remote_mcp.allowed_scopes. Preview, paper, and live tools are visible only when their explicit scopes are granted, and the runtime gates still run before any broker write boundary.

ToolMinimum scope
ibkr_healthibkr:health:read
ibkr_backend_statusibkr:health:read
ibkr_session_requirementsibkr:health:read
ibkr_session_renewibkr:health:read
ibkr_kill_switch_statusibkr:health:read
ibkr_accounts_listibkr:accounts:read
ibkr_account_metadataibkr:accounts:read
ibkr_account_summaryibkr:portfolio:read
ibkr_pnl_dailyibkr:portfolio:read
ibkr_pnl_realtimeibkr:portfolio:read
ibkr_positions_listibkr:positions:read
ibkr_portfolio_snapshotibkr:portfolio:read
ibkr_contracts_searchibkr:marketdata:read
ibkr_contract_resolveibkr:marketdata:read
ibkr_market_snapshotibkr:marketdata:read
ibkr_historical_barsibkr:marketdata:read
ibkr_options_chainibkr:options:read
ibkr_option_greeksibkr:options:read
ibkr_market_depthibkr:marketdata:depth:read
ibkr_scanner_runibkr:scanner:read
ibkr_news_listibkr:news:read
ibkr_news_articleibkr:news:read
ibkr_fundamentals_getibkr:fundamentals:read
ibkr_market_sessionibkr:calendar:read
ibkr_market_holidaysibkr:calendar:read
ibkr_currency_rateibkr:currency:read
ibkr_transfer_historyibkr:transfers:read
ibkr_orders_listibkr:orders:read
ibkr_orders_historyibkr:orders:read
ibkr_order_statusibkr:orders:read
ibkr_executions_listibkr:orders:read
ibkr_limits_statusibkr:risk:read
ibkr_audit_tailibkr:audit:read
ibkr_audit_exportibkr:audit:export
ibkr_order_previewibkr:orders:preview
ibkr_bracket_order_previewibkr:orders:preview
ibkr_paper_order_submitibkr:orders:paper:submit
ibkr_paper_order_cancelibkr:orders:paper:cancel
ibkr_paper_order_modifyibkr:orders:paper:modify
ibkr_paper_bracket_order_submitibkr:orders:paper:submit
ibkr_approvals_createibkr:approvals:create
ibkr_live_order_submitibkr:orders:live:submit
ibkr_live_order_cancelibkr:orders:live:cancel
ibkr_live_order_modifyibkr:orders:live:modify
ibkr_live_bracket_order_submitibkr:orders:live:submit

Denials

Missing scope returns AUTH_MISSING_SCOPE and emits a denied-scope audit event. Unknown local or remote scopes fail configuration validation.

Sidecar Relay

The sidecar relay lets a remote MCP gateway route authorized broker requests to a local Client Portal Gateway without exposing IBKR session material remotely.

Required gates for a remote deployment:

  • remote MCP OAuth is already enabled and validates the MCP client token
  • the local sidecar has an explicit pairing record for the remote instance
  • the relay session is bound to the paired sidecar id
  • heartbeat is valid and the relay session has not expired
  • the local Client Portal Gateway session is usable

The sidecar does not automate retail IBKR browser login. If the local Client Portal Gateway requires authentication, the remote gateway must return a manual local action requirement instead of attempting login automation.

Sensitive material is not forwarded to MCP clients. Forwarded broker requests carry the tool name, required scope, request id, and a payload hash. Cookies, authorization headers, tokens, credentials, secrets, and local filesystem paths are refused before forwarding.

Typical local flow:

ibkr-agent sidecar identity create --public-key <public-key> --display-name laptop --json
ibkr-agent sidecar pairing create \
  --remote-instance-id remote-1 \
  --sidecar-id sidecar-example \
  --user-id local-user \
  --ttl-seconds 300 \
  --json
ibkr-agent sidecar session create \
  --remote-instance-id remote-1 \
  --sidecar-id sidecar-example \
  --ttl-seconds 300 \
  --json
ibkr-agent sidecar relay accept \
  --remote-instance-id remote-1 \
  --sidecar-id sidecar-example \
  --tool-name ibkr_accounts_list \
  --scope ibkr:accounts:read \
  --payload-json '{}' \
  --json

The CLI currently exposes relay primitives for operator and integration workflows: identity creation, pairing records, relay sessions, and request sanitization. It is not a long-running sidecar daemon and it does not persist or heartbeat a local relay process by itself.

Both the CLI YAML config and the SDK GatewayConfiguration model sidecar enablement as fail-closed. A remote deployment must satisfy all of:

  • sidecar.enabled: true (CLI YAML or SDK config);
  • safety.sidecar_enabled: true — the independent safety flag is now loaded from CliConfigFile.safety.sidecar_enabled and the SDK GatewayConfiguration.safety.sidecar_enabled;
  • sidecar.remote_relay_url and sidecar.local_client_portal_base_url configured;
  • sidecar.heartbeat_timeout_seconds > sidecar.heartbeat_interval_seconds;
  • remote MCP OAuth already enabled and validated.

If any item is missing the gateway refuses to start with CONFIG_SIDECAR_FORBIDDEN or CONFIG_INVALID before serving relay traffic.

Provider Compatibility

Provider compatibility is a test and configuration layer around MCP. It does not add provider SDKs to broker, risk, auth, audit, backend, or MCP core crates, and it does not change broker execution semantics.

Supported targets:

  • generic MCP inspector over stdio
  • Cursor MCP client over stdio
  • Continue MCP client over stdio
  • OpenAI remote MCP over HTTP with OAuth bearer auth
  • Anthropic MCP connector over HTTP with OAuth bearer auth

Local clients use the same stdio command shape:

ibkr-agent mcp serve --transport stdio --describe --json
ibkr-agent mcp serve --transport stdio

Use --describe only for smoke checks. Real local clients should omit it so the command stays attached to stdio. Local discovery is filtered by enabled scopes, and every tool call is audited.

Example client configuration files are available under examples/mcp-clients/:

  • generic-inspector.json
  • cursor.json
  • continue.json

These examples reference IBKR_CONFIG only. They must not embed IBKR credentials, Client Portal Gateway cookies, OAuth tokens, refresh tokens, broker session ids, or local absolute paths.

Remote provider connectors should use the remote MCP HTTP endpoint documented in docs/remote-mcp-oauth.md. The provider receives the gateway protected resource metadata and sends an OAuth bearer token for the MCP request. The gateway validates issuer, audience, expiry, signature, and tool scope before any broker access. MCP bearer tokens are never forwarded to IBKR and are never written to audit payloads.

Provider-specific behavior belongs in src/internal/provider_compat/ tests or examples. Core implementation stays provider-neutral; compatibility is proven through:

  • schema snapshots generated from the broker MCP tool registry
  • auth denial snapshots for missing token and missing scope
  • redaction snapshots for provider-visible outputs and example configs
  • dependency checks that forbid provider SDK dependencies in production crates

The provider compatibility harness exercises representative tool discovery, schema, redaction, auth denial, and provider-visible output behavior. Order preview, paper order, and live trading provider flows remain governed by their own policy, approval, scope, idempotency, risk, kill-switch, and audit gates.

Order Preview

Order preview is write-adjacent but still non-executable: the gateway can validate an intent and produce a preview, but it cannot submit, cancel, approve, modify, or create a broker-side order.

CLI

Preview is disabled by default. A local run must opt in explicitly:

ibkr-agent orders preview \
  --account DU1234567 \
  --symbol AAPL \
  --side buy \
  --quantity 1 \
  --limit-price 100 \
  --currency USD \
  --enable-preview \
  --json

Without --enable-preview, the command returns ORDER_PREVIEW_DISABLED.

MCP

The MCP registry advertises ibkr_order_preview when ibkr:orders:preview is present in the active local scope set or remote bearer token grant. The tool uses the same validation and risk path as the CLI and returns a persisted preview_id for later approval-bound paper or live flows. The persisted validated order includes the resolved contract id, symbol, and asset class so live allowlist checks evaluate the actual previewed instrument.

MCP preview accepts limit, stop, stop_limit, and trailing_stop candidates. Limit candidates require limit_price; stop candidates require stop_price; stop-limit candidates require both; trailing-stop candidates require trailing_amount or trailing_percent. Market candidates remain refused by the default deterministic policy.

Generic submit, cancel, approve, and modify tool names remain forbidden; use the explicit preview, paper, and live-gated tools.

Risk Policy

Risk checks are deterministic. The default policy is disabled and therefore fails closed. Enabled policy checks currently cover account mode, asset class, positive quantity, fractional quantity, quantity limit, order type, required price fields for the selected order type, trailing offsets, and estimated notional.

Audit

Preview work emits preview-phase audit shapes:

  • order.intent.received
  • order.risk.checked
  • order.preview.created
  • order.preview.refused

Audit payloads must stay redacted and must not contain broker session material, tokens, cookies, credentials, sensitive headers, or local secret paths.

Paper Orders

Paper orders are the paper-only write workflow. They do not enable live trading.

Required Gates

Paper submit, cancel, and modify require:

  • explicit paper trading enablement
  • a paper account in the allowlist
  • paper submit, cancel, or modify scope
  • a persisted approval record for submit
  • an idempotency key
  • audit events for approval, submit, cancel, modify, and lifecycle transitions
  • a configured paper writer for broker-side submit/cancel/modify when validating against Client Portal Gateway

Paper workflows do not enable live trading. Live submit/cancel/modify use the separate live-gated commands and MCP tools, with independent config, scope, approval, risk, kill switch, audit, and paper-to-live gates.

CLI

Create a local approval record:

ibkr-agent approvals create \
  --account DU1234567 \
  --preview-id <preview_id> \
  --ttl-seconds 300 \
  --json

Submit a paper order candidate:

ibkr-agent orders submit \
  --account DU1234567 \
  --approval-id <approval_id> \
  --idempotency-key paper-submit-001 \
  --enable-paper \
  --json

Cancel a paper order candidate:

ibkr-agent orders cancel \
  --account DU1234567 \
  --broker-order-id paper-order-local \
  --idempotency-key paper-cancel-001 \
  --enable-paper \
  --json

Without --approval-id, paper submit returns PAPER_APPROVAL_REQUIRED. Approvals are bound to one persisted preview and are consumed after a successful submit; reuse with a fresh idempotency key returns APPROVAL_CONSUMED. Without --enable-paper, paper submit and cancel return a typed disabled refusal. Approval and idempotency records are persisted in the configured audit SQLite database so replays remain stable across CLI invocations.

The CLI defaults to LocalCandidatePaperWriter for offline smoke tests. Runtime deployments can wire ClientPortalPaperWriter to exercise the real paper account path through the Client Portal Gateway before live trading is enabled.

MCP

The MCP registry advertises explicit paper tools when their scopes are present in the active local scope set or remote bearer token grant:

ToolScope
ibkr_paper_order_submitibkr:orders:paper:submit
ibkr_paper_order_cancelibkr:orders:paper:cancel
ibkr_paper_order_modifyibkr:orders:paper:modify

Paper submit still requires a persisted approval for a preview, an idempotency key, and paper trading enablement. Paper cancel requires an idempotency key and paper cancel scope. Paper modify requires account_id, broker_order_id, idempotency_key, and at least one bounded change (quantity, limit_price, stop_price, time_in_force, trailing_amount, or trailing_percent). Generic ibkr_order_submit, ibkr_order_cancel, ibkr_order_modify, and ibkr_order_approve remain forbidden.

Idempotency

Paper submit/cancel/modify requests must include idempotency keys. Replaying the same key with the same canonical request is treated as replay. Reusing the same key with a different request is refused with PAPER_IDEMPOTENCY_CONFLICT. Before the broker writer is called, the gateway stores a pending idempotency record. If the process crashes before the final receipt is recorded, the same key refuses retry until recovery resolves the pending broker-side state. At CLI startup, pending submit records are recovered by checking broker order status with the original idempotency key, which is sent to IBKR as cOID.

Broker Response Mapping

Paper writer receipts drive the persisted lifecycle status rather than fixed values. Paper submit maps Rejected/Refused/Inactive broker statuses to Refused; paper cancel and modify refuse with BROKER_RESPONSE_INVALID when the broker did not accept the request and the broker-reported status is not terminal. Successful order workflow completion (idempotency record, live reconciliation backlog when applicable, and approval consumption) is committed in a single SQLite transaction, so a crash window cannot leave an approval in Approved state after a successful submit.

Provider Approval UX

Provider-side approval prompts are optional client UX only. They may help a human understand what an agent is about to request, but they are not a security boundary for this gateway.

The gateway must still enforce its own gates before broker access:

  • OAuth bearer validation for remote MCP clients
  • explicit tool scope checks
  • local safety flags for disabled feature classes
  • order preview policy and non-executable preview records
  • paper order approval records and idempotency checks
  • audit events for allowed and refused operations
  • redaction of tokens, cookies, credentials, headers, and local paths

A provider approval prompt cannot grant missing gateway scopes, enable disabled config, authorize paper trading, or permit live trading. If a provider says a tool call was approved but the gateway policy refuses it, the gateway refusal wins and returns the stable error code for that gate.

Provider UX should display gateway-visible facts only: tool name, required scope, user-facing parameters, preview or approval id when one exists, and the stable refusal or success shape. It must not display or persist broker cookies, OAuth bearer tokens, Client Portal Gateway session material, raw headers, credentials, or local filesystem paths.

Any future provider-specific approval integration must remain outside broker core and should live in compatibility examples or adapters. Broker, risk, approval, audit, auth, and MCP core semantics stay provider-neutral.

MCP-native ibkr_approvals_create creates a gateway approval record, not a provider UI approval. The tool is scoped with ibkr:approvals:create, loads the preview from server storage, rejects missing, mismatched, or expired previews, and then persists the approval to the audit database. Provider prompts may explain that action to a human, but they cannot replace the stored gateway approval record consumed by paper and live submit flows.

Paper-to-Live Checklist

Live trading remains disabled until the operator acknowledges this checklist in configuration and the live request supplies every runtime gate.

For the explicit split between items the gateway enforces mechanically (config validation, runtime gates) and items the operator must verify in the deployed environment, see the “Code-Enforced Gates vs Operator-Verified Checks” subsection of production-readiness.md.

Before setting live_trading.enabled: true and safety.live_trading_enabled: true:

  • run the full paper submit/cancel flow with the same account family
  • verify approval records are one-use, unexpired, and account-matched
  • configure a live account allowlist with only intended live accounts
  • configure a live risk policy id and hard limits for notional, quantity, symbol, asset class, frequency, and session exposure
  • test the kill switch close/open path
  • verify audit storage is writable and live retention is configured
  • review the live incident runbook

Live submit requires all of:

  • explicit live config and independent safety flag
  • allowlisted live account
  • live submit scope
  • unexpired validated order preview with resolved symbol and asset class
  • matching approval record
  • idempotency key
  • passing live risk policy
  • open kill switch
  • audit availability
  • acknowledged paper-to-live checklist

If any item is missing, the gateway refuses before broker execution.

Live Trading Runbook

Live trading is an operator-controlled mode. It is not enabled by default and must be reversible at runtime through the kill switch.

Enablement

  1. Complete docs/paper-to-live.md.
  2. Configure live_trading.enabled: true.
  3. Add only intended live accounts to live_trading.allowed_accounts.
  4. Set live_trading.risk_policy_id to the deployed live policy.
  5. Set live_trading.paper_to_live_checklist_acknowledged: true.
  6. Set safety.live_trading_enabled: true.
  7. Confirm audit retention keeps immutable live write events for at least 2555 days and requires export before purge.

Emergency Disable

Close the live kill switch immediately when an unexpected order, policy gap, broker session issue, audit failure, or operator uncertainty appears. A closed kill switch refuses live submit, cancel, modify, and bracket submit before broker execution.

After emergency disable:

  • keep audit storage intact
  • record the operator, timestamp, reason, request ids, account id hash, and affected broker order ids
  • stop provider or MCP clients that initiated the flow
  • review the last successful preview, approval, submit, cancel, modify, bracket, and audit events
  • reopen live trading only after limits, scopes, approvals, and audit have been verified again

Live Modify

ibkr_live_order_modify is a bounded adjustment path for existing broker orders. It avoids cancel-and-resubmit races, but it is still a live write: the handler requires the live modify scope, enabled live config, allowlisted account, open kill switch, audit availability, and acknowledged paper-to-live checklist. The MCP payload carries only account_id, broker_order_id, approval_id, preview_id, idempotency_key, and bounded changes. The approval must reference the replacement preview loaded by the server; modify requests without that approval path fail before the writer boundary. Empty modify requests are also rejected before pending idempotency state is written.

Live Bracket

ibkr_live_bracket_order_submit is an MCP-only grouped write for parent, take-profit, and stop-loss legs. It requires approved server-persisted previews for all three legs and rejects approvals mixed from different bracket preview groups. It evaluates live limits per leg, inserts durable pending idempotency state before the writer boundary, and consumes all three approvals after a successful result. The bundled group writer submits legs sequentially through the configured LiveOrderWriter; it is not broker-native OCA atomicity.

Incident Review Template

  • Incident timestamp:
  • Operator:
  • Account id hash:
  • Tool name:
  • Request id:
  • Approval id:
  • Idempotency key:
  • Broker order id:
  • Kill switch state before incident:
  • Kill switch state after incident:
  • Live limit policy id:
  • Refusal or execution status:
  • Audit event ids:
  • Root cause:
  • Follow-up changes:

Production Readiness

This checklist is for operators and developers preparing a real deployment of ibkr-agent-gateway.

The package is intentionally conservative, but it is still unofficial software for financial workflows. Treat production enablement as an explicit deployment decision, not as a default mode.

Current Deployment Status

Ready for production-like validation:

  • SDK facade with fake and Client Portal Gateway backend constructors;
  • CLI runtime config loading for fake or Client Portal Gateway backends;
  • read-only broker data paths;
  • redacted audit storage and export;
  • local MCP stdio serving with scope-filtered tool discovery and audited calls;
  • remote MCP OAuth/OIDC validation primitives;
  • preview, paper, sidecar, provider compatibility, and live-gate domain logic;
  • live MCP submit/cancel handlers that load approval, preview, policy, writer, market snapshot, and audit state server-side;
  • live MCP modify and bracket handlers that require approved replacement/group previews, live limit checks, durable pending idempotency, and approval consumption before they are considered complete;
  • mature MCP read surface for PnL, order history, account metadata, options, greeks, depth, scanners, news, fundamentals, market sessions, FX rates, and transfer history;
  • MCP approval creation for existing unexpired previews, scoped separately from provider UI prompts;
  • live order lifecycle reconciliation with a SQLite pending-order backlog;
  • live order writer trait with a bundled Client Portal Gateway implementation that returns broker-generated order ids and handles the IBKR reply-chain confirmation protocol.

Live submit, cancel, modify, and bracket flows delegate broker writes to a LiveOrderWriter implementation or group writer built from it:

  • ClientPortalLiveWriter for production deployments behind a real Client Portal Gateway;
  • LocalCandidateLiveWriter for CLI smoke tests and offline development — it returns a deterministic local identifier and performs no network I/O;
  • RefusingLiveWriter as a fail-closed default for environments intentionally kept out of broker execution.

The CLI orders live-submit --enable-live / orders live-cancel --enable-live commands default to LocalCandidateLiveWriter for offline smoke tests. Operators can select --live-broker client-portal to use ClientPortalLiveWriter with a configured Client Portal Gateway backend, or --live-broker refusing for fail-closed checks. MCP live modify uses the configured live writer. MCP live bracket uses SequentialLiveOrderGroupWriter, which delegates each leg to the configured live writer and does not claim broker-native OCA atomicity.

Hard Prerequisites

Before exposing any non-local workflow:

  • run the full validation suite:

    cargo fmt --check
    cargo clippy --workspace --all-targets --features unstable-internal-test-support -- -D warnings
    cargo test --workspace --features unstable-internal-test-support
    cargo test --workspace --features unstable-internal-test-support secret
    cargo doc --workspace --no-deps
    
  • verify the exact binary/library artifact that will be deployed;

  • configure audit storage and verify writes, tail reads, and exports;

  • run ibkr-agent audit verify against the target audit DB to confirm the chained HMAC log is intact;

  • verify CLI --config loading with a missing-path negative test and a real config smoke test;

  • supply stable deployment HMAC secrets from a secret manager;

  • confirm no broker cookies, bearer tokens, credentials, raw headers, local paths, raw account ids, or Client Portal Gateway session material appear in CLI, MCP, logs, fixtures, or audit output;

  • keep broker authentication separate from MCP/OAuth authorization.

Required Secrets

Use deployment-specific secrets. Do not commit them.

SecretPurpose
IBKR_AUDIT_HMAC_SECRETstable HMAC key for account/audit correlation
IBKR_REMOTE_TOKEN_HMAC_SECRETHMAC key for remote token-id audit hashes

Secrets should be at least 32 random bytes. Rotate only with a plan for audit correlation discontinuity.

Read-Only Broker Deployment

For real broker reads:

  • run the Interactive Brokers Client Portal Gateway locally or in the intended private network boundary;
  • complete IBKR authentication outside this gateway;
  • use GatewayConfig::client_portal(url) or a verified runtime config path;
  • allow verify_tls=false only for localhost URLs;
  • test session required, expired, unavailable, and keepalive behavior before using account or market-data tools;
  • validate the richer Spec 009 Client Portal mappings for options, scanners, news, fundamentals, calendar/session, FX, and transfers against the exact deployed Client Portal Gateway version before relying on them operationally. The repository has fixture-backed mapper coverage and wiremock contracts for the expected CPAPI paths/query parameters, but broker endpoint availability can vary by IBKR deployment and entitlement.

Remote MCP

Remote MCP requires:

  • remote_mcp.enabled: true;
  • safety.remote_mcp_enabled: true in CLI YAML config, or safety.remote_public_mcp_enabled: true when constructing GatewayConfiguration directly from Rust;
  • HTTPS issuer and JWKS URLs;
  • RS256 JWTs against RSA JWKS keys in production builds;
  • accepted audiences/resources;
  • explicit allowed gateway scopes;
  • remote_mcp.token_id_hmac_secret_env in CLI YAML config, or a populated RemoteMcpConfig::token_id_hmac_secret in SDK config;
  • configured gateway rate limiting with remote_mcp.rate_limit_max_requests and remote_mcp.rate_limit_window_seconds;
  • a bounded connection cap with remote_mcp.max_connections, plus upstream connection limits for internet-facing deployments.

Remote MCP bearer tokens must never be forwarded to IBKR or stored raw in audit.

Sidecar Relay

Use sidecar relay only when:

  • remote MCP is already validated;
  • a sidecar identity and pairing record exist;
  • heartbeat and relay session binding are verified;
  • forwarded payloads contain hashes and safe tool metadata only;
  • local Client Portal Gateway login still happens manually outside the gateway.

Preview and Paper Trading

Order preview is non-executable. It must remain useful for validation without creating broker-side state.

Paper submit/cancel/modify require:

  • explicit paper enablement;
  • paper scopes;
  • persisted approval records;
  • idempotency keys;
  • audit availability;
  • refusal tests for disabled config, missing approval, and idempotency conflicts.

MCP-created approvals require ibkr:approvals:create and an existing unexpired server-side preview. Provider approval prompts remain display-only UX and do not replace gateway approval records.

Live Trading

Do not enable live trading until all items in paper-to-live.md and live-runbook.md are satisfied.

Code-Enforced Gates vs Operator-Verified Checks

The gateway enforces most of the live readiness contract mechanically. The remaining items require deployment-side validation that no library can perform on the operator’s behalf.

Config-validated at startup — the gateway refuses to start otherwise:

  • live_trading.enabled: true;
  • safety.live_trading_enabled: true — an independent flag deliberately separated from live_trading.enabled so a single misconfigured value cannot unlock live trading on its own. The gateway refuses if one is set without the other;
  • live_trading.allowed_accounts is non-empty;
  • live_trading.risk_policy_id is set;
  • live_trading.paper_to_live_checklist_acknowledged: true;
  • audit.live_write_retention_days >= 2555 (see audit-retention.md).

Runtime gates evaluated on every live submit/cancel/modify/bracket — the request is refused before the writer is invoked:

  • live submit, cancel, modify, or bracket submit scope granted;
  • target account present in live_trading.allowed_accounts loaded from the validated runtime configuration;
  • approval record one-use, unexpired, account-matched;
  • idempotency key present (forwarded to the broker as cOID for broker-side de-duplication);
  • validated order preview not expired;
  • live risk policy passes for the approved order or bracket legs (notional, quantity, symbol, asset class, frequency, session exposure, price collar, and quote freshness);
  • live frequency/session counters and session notional are derived from durable audit workflow state before risk evaluation, not trusted from caller input; notional counters are deterministic limit-price exposure counters, so market, stop, and trailing-stop orders without limit_price are covered by order count, quantity, symbol/asset-class, price-collar, and quote-freshness gates rather than session-notional arithmetic;
  • kill switch open;
  • audit storage available;
  • paper-to-live migration checklist acknowledged on the request (paper_trading_validated, approvals_reviewed, limits_reviewed, kill_switch_tested, incident_runbook_reviewed).

Operator-verified before flipping the safety flag — no library can check these for you:

  • run the full paper submit/cancel/modify flow against the same account family;
  • close and reopen the kill switch in the deployed environment and confirm refusal during the closed window;
  • confirm the audit storage actually writes to its target volume and that retention export is automated before purge (the 2555-day floor is validated by config, but the export pipeline is your responsibility);
  • validate ClientPortalLiveWriter against your IBKR paper environment before promoting to a live account;
  • review and rehearse live-runbook.md emergency procedures with the on-call operator.

Close the kill switch on uncertainty.

Live order writer wiring

When the gates pass, the live flow delegates the broker call to the configured writer boundary. CLI deployments select the bundled Client Portal writer with --live-broker client-portal and a broker.backend: client_portal_gateway config. Library consumers should treat the public LiveOrderWriter trait as the stable extension point; the bundled Client Portal adapter remains an internal CLI/runtime adapter unless it is promoted through src/public/*.

Live submit, modify, and bracket submit also require a server-side LivePolicyRegistry. The request only names live_trading.risk_policy_id; the gateway loads the corresponding LiveLimitPolicy from trusted runtime configuration before evaluating limits. The live policy should keep max_price_deviation_bps and max_quote_age_seconds enabled so live writes refuse stale or out-of-band quotes instead of relying only on notional limits.

Successful live submits and non-terminal live modifies are stored in the SQLite live_orders_pending backlog. The MCP runtime polls IbkrBackend::order_status on live_trading.reconciler_interval_seconds, records lifecycle transitions, and removes terminal orders from the backlog. Startup also rebuilds the backlog from completed live idempotency records so non-terminal orders remain tracked after a restart.

The bundled Client Portal writer:

  • posts orders to /iserver/account/{accountId}/orders with cOID set to the idempotency key for broker-side de-duplication;
  • handles the reply chain (POST /iserver/reply/{replyId}) up to a configurable depth (default 5) so warning prompts are confirmed once;
  • refuses market orders and missing limit prices at the writer boundary, in addition to the upstream risk gates;
  • returns the broker-generated order id or status in the lifecycle record;
  • maps 401 to BROKER_SESSION_REQUIRED, transport failures to BROKER_BACKEND_UNAVAILABLE, and oversized responses to BROKER_RESPONSE_INVALID.

Validate the writer in a paper environment before promoting to live. The contract test suite (tests/contract_cpapi_live_writer.rs) covers the happy path, reply confirmation, depth limit, market/limit refusals, 401, decimal serialization, broker error fields, and cancel response parsing. Contextual read CPAPI contracts (tests/contract_cpapi_contextual_reads.rs) cover the expected paths and query parameters for options, greeks, market depth, scanners, news, fundamentals, calendar/session, FX, transfer history, and encoded query values. Treat those paths as gateway adapter contracts, not proof that every endpoint exists in the currently deployed IBKR Client Portal Gateway build; verify contextual reads against the exact IBKR Gateway version and entitlements before enabling them for production operations.

Package Publication

Pre-Publish Runbook

Run the full gate suite, confirm the tarball contents, then tag and publish:

# 1. Quality gates
cargo fmt --check
cargo clippy --workspace --all-targets --features unstable-internal-test-support -- -D warnings
cargo test --workspace --features unstable-internal-test-support
cargo test --workspace --features unstable-internal-test-support secret
cargo doc --workspace --no-deps
cargo audit

# 2. Tarball contents
cargo package --allow-dirty --no-verify --list
cargo publish --dry-run --locked

# 3. Tag and publish
git tag -a vX.Y.Z -m "vX.Y.Z — release notes"
git push origin vX.Y.Z
cargo publish --locked

Tarball Contents

The package must not include local agent directories, editor state, build outputs, private configs, broker session files, tokens, or machine-specific paths. The include list in Cargo.toml is the source of truth — it enumerates exactly what ships and fails closed for everything else.

Verify after every change that touches the repo root:

cargo package --allow-dirty --no-verify --list | head

Audit Log

The gateway records security-relevant activity as redacted, append-only SQLite rows. Audit is used for read operations, scope denials, preview/risk decisions, paper lifecycle transitions, remote auth events, sidecar forwarding, live submit/cancel/modify and bracket lifecycle events, and reconciled live lifecycle transitions.

Storage

The SQLite schema lives under src/internal/audit/migrations/. Live order reconciliation adds a live_orders_pending backlog keyed by account id and broker order id.

SqliteAuditWriter configures WAL journaling, writes redacted payload JSON, and stores a chained HMAC hash for tamper-evidence across appended rows.

Review and Export

ibkr-agent audit tail --limit 100 --json
ibkr-agent audit tail --database-url sqlite:/path/to/audit.db --limit 100 --json
ibkr-agent audit export --database-url sqlite:/path/to/audit.db --limit 500 --json
ibkr-agent audit verify --json
ibkr-agent audit verify --database-url sqlite:/path/to/audit.db --hmac-secret-env IBKR_AUDIT_HMAC_SECRET --json

MCP clients use ibkr_audit_tail with ibkr:audit:read. CLI audit tail, audit export, and audit verify are also scope-gated by ibkr:audit:read when a runtime config is supplied, and each command appends a redacted audit event for the audit read/verification action itself.

audit verify scans the full chained HMAC log and exits with code 2 when the chain is broken. External database verification requires the original audit HMAC key through --hmac-secret-env; the runtime database uses the active CLI configuration key automatically.

Redaction

Audit payloads must not store:

  • bearer tokens;
  • cookies;
  • credentials;
  • sensitive headers;
  • local secret paths;
  • raw Client Portal Gateway session material;
  • raw account ids.

Account and token correlation use HMAC-SHA256. Free-form audit metadata is scrubbed by sensitive field name before persistence. Field-name matching is case-insensitive and substring-based, so broad markers such as path and header intentionally redact conservative matches rather than risk leaking local paths or sensitive headers.

Denied, refused, failed, and completed operations keep a consistent correlation shape so review can reconstruct what happened without exposing broker secrets.

Live Reconciliation

Successful live submits and non-terminal live modifies are added to the reconciliation backlog. The MCP stdio runtime calls reconcile_live_orders_once on the configured interval (live_trading.reconciler_interval_seconds, default 5) to poll IbkrBackend::order_status, append live_order_lifecycle_changed events on status transitions, and remove filled/cancelled/refused orders from the backlog. On startup, the runtime also rebuilds the backlog from completed live idempotency records so existing non-terminal live orders remain tracked after a restart.

The same durable live idempotency records provide server-side frequency and session counters for live submit gates. CLI and MCP submit paths overwrite the caller context counters before evaluation.

Audit Retention and Backup

Audit data is operational evidence. It must remain redacted, immutable for live write workflows, and exportable without raw broker secrets.

Retention

  • Read-only, preview, remote-auth, sidecar, provider, and paper events should be retained according to operator policy and storage capacity.
  • Live write events must be retained for at least 2555 days.
  • Live write events must be immutable after append.
  • Purge jobs must require a prior export.
  • Exports must include event ids, request/session correlation, tool names, scopes, decisions, result status, stable error codes, input/output hashes, and redaction metadata.

Backup

For SQLite deployments:

  1. Stop writers or use a consistent SQLite backup mechanism.
  2. Export recent or full audit records with ibkr-agent audit export --json.
  3. Store the JSONL payload and file hash together.
  4. Verify the export contains no raw tokens, cookies, credentials, local paths, broker session material, or raw account ids.
  5. Store backups in access-controlled storage separate from application logs.

Restore and Replay

Replay must use redacted fixtures only. Restored audit exports are evidence for debugging and regression tests; they are not a broker data cache and must not recreate live broker sessions.

Incident Review

Use this template for refused or unexpected broker-gateway behavior, including remote MCP auth failures, sidecar relay failures, paper order issues, live gate refusals, and live kill switch events.

Immediate Actions

  • Close the live kill switch if live trading may be affected.
  • Preserve audit storage and avoid destructive cleanup.
  • Export relevant audit records as redacted JSONL.
  • Record request ids, event ids, approval ids, idempotency keys, and account id hashes only.
  • Stop any provider, MCP, or sidecar client that is repeating unsafe requests.

Review Template

  • Incident id:
  • Time range:
  • Summary:
  • Affected tools:
  • Affected topology:
  • Audit event ids:
  • Request ids:
  • Account id hashes:
  • Approval ids:
  • Idempotency keys:
  • Stable error codes:
  • Kill switch state before:
  • Kill switch state after:
  • Replay fixture path:
  • Expected decision:
  • Actual decision:
  • Root cause:
  • Customer/operator impact:
  • Follow-up actions:
  • Owner:
  • Due date:

Replay Expectations

Replay fixtures must contain redacted audit events and expected structured decisions only. They must not contain raw bearer tokens, cookies, credentials, broker session material, local filesystem paths, or live broker dependencies.

Testing

The project is validated through Cargo-discoverable tests, fake Client Portal Gateway fixtures, replay checks, provider snapshots, and local performance budgets.

Required Local Gates

cargo fmt --check
cargo clippy --workspace --all-targets --features unstable-internal-test-support -- -D warnings
cargo test --workspace --features unstable-internal-test-support
cargo test --workspace --features unstable-internal-test-support secret

CI also runs documentation and security workflows.

CPAPI Contracts

Wiremock contract tests lock the Client Portal Gateway HTTP boundary for:

  • live and paper writer POST/DELETE/modify requests;
  • contextual read paths and query parameters for options, greeks, market depth, scanners, news, fundamentals, market sessions/holidays, FX rates, and transfer history.

Fixture Coverage

Fake CPAPI fixtures under tests/fixtures/cpapi/ cover:

  • session usable, missing, expired, keepalive success, and keepalive expiry;
  • accounts list;
  • portfolio snapshot, PnL, account metadata, and positions;
  • stock/ETF contract search and ambiguity;
  • live, delayed, and stale market snapshots;
  • historical bars;
  • read-only orders, order history, order status, and executions;
  • options chain, greeks, market depth, scanners, news, fundamentals, market session/holidays, FX rates, and transfer history.

Fixtures must not contain tokens, cookies, credentials, sensitive headers, local secret paths, bearer values, or raw broker session material.

Feature Coverage

The test suite covers:

  • CLI contracts for read commands, audit, preview, paper, and live-gated refusals;
  • MCP tool discovery, schemas, redaction, keepalive, and scope denials;
  • remote OAuth RS256 validation, token redaction, generic auth denials, configurable rate limiting, and connection-cap handling;
  • order preview, risk checks, paper approval/idempotency, paper modify, live limits, live modify, bracket submit, kill switch, and paper-to-live gates;
  • sidecar identity, pairing, heartbeat, forwarding safety, and secret scans;
  • provider compatibility snapshots and provider SDK dependency boundaries.

Replay and Performance

Replay tests check audit redaction and secret-scan behavior. Performance tests assert budgets for fake backend reads, audit append/tail, cached remote OAuth validation, prepared remote MCP authorization, live gate/risk/idempotency, and sidecar request safety.

To measure the full offline suite duration locally:

time cargo test --workspace --features unstable-internal-test-support

The security workflow filters tests by secret while still enabling unstable-internal-test-support, because several secret/redaction regression tests intentionally use hidden internal fixtures and helpers.