Skip to main content

Bybit v5 broker MCP server with fail-closed consent gate

Project description

bybit-mcp

CI License: MIT Python 3.11+ Code style: ruff

Bybit v5 broker MCP server for Vibe-Trading.

Exposes 17 tools to an MCP-aware agent (LLM):

  • 11 read (market data + private account snapshots) — no mandate gate.
  • 6 write (place / amend / cancel / cancel-all / set leverage / set TP/SL) — gated by the user-side mandate, fail-closed.

The mandate file, kill switch, daily counter, and audit ledger live in the same filesystem layout Vibe-Trading's in-repo LiveOrderGuardTool reads from, so the in-process guard and the bybit-mcp guard see the same state and the audit ledger is unified.

Quick start

# 1. Install
cd ~/eth/bybit-mcp
pip install -e ".[dev]"

# 2. Configure
cp .env.example .env
$EDITOR .env                  # set BYBIT_API_KEY / BYBIT_API_SECRET

# 3. Commit a mandate
mkdir -p ~/.vibe-trading/live/bybit
$EDITOR ~/.vibe-trading/live/bybit/mandate.json    # see schema below

# 4. Run (stdio for embedding into Vibe-Trading)
python -m bybit_mcp.server.mcp_server --transport stdio

# 4b. Or run over HTTP for curl testing
python -m bybit_mcp.server.mcp_server --transport http --port 8001

Tool surface

Tool Type Gated Notes
bybit_get_tickers read no public, no auth
bybit_get_kline read no public, no auth
bybit_get_orderbook read no public, no auth
bybit_get_funding_history read no public, no auth
bybit_get_open_interest read no public, no auth
bybit_get_instruments_info read no public, no auth
bybit_get_recent_trades read no public, no auth
bybit_get_wallet_balance read no signed, needs API creds
bybit_get_positions read no signed, needs API creds
bybit_get_open_orders read no signed, needs API creds
bybit_get_order_history read no signed, needs API creds
bybit_place_order write yes deny on missing mandate / kill switch / cap breach
bybit_amend_order write yes enforces the LARGER of (explicit notional, new_qty × live price)
bybit_cancel_order write yes single order
bybit_cancel_all_orders write yes bulk
bybit_set_leverage write yes structural check only
bybit_set_trading_stop write yes TP/SL price change does not alter notional math

Mandate file

A valid mandate.json is required at <runtime_root>/live/bybit/mandate.json before any write tool is callable. Without it, every order attempt returns status="blocked", decision="deny", reason="no valid mandate on file".

The schema matches Vibe-Trading's src.live.mandate.model.Mandate EXACTLY so a mandate committed by the Vibe-Trading consent UX is readable by bybit-mcp without translation, and vice versa.

{
  "schema_version": 1,
  "hard_caps": {
    "account_funding_usd": 5000.0,
    "max_order_notional_usd": 1000.0,
    "max_total_exposure_usd": 5000.0,
    "max_leverage": 3.0,
    "allowed_instruments": ["crypto"],
    "max_trades_per_day": 50
  },
  "universe": {
    "asset_classes": ["crypto"],
    "min_market_cap_usd": null,
    "min_avg_daily_volume_usd": null,
    "exclude_symbols": ["DOGEUSDT"]
  },
  "consent": {
    "created_at": "2026-06-01T00:00:00+00:00",
    "consent_token_sha256": "<sha256 of the user-side consent artifact>",
    "broker": "bybit",
    "account_ref": "bybit-acct-0001",
    "expires_at": "2026-07-01T00:00:00+00:00"
  },
  "flatten_on_halt": false
}

6-step fail-closed gate

Every write tool runs the same gate (mirrors Vibe-Trading's src.live.order_guard.LiveOrderGuardTool):

  1. load_mandate — no mandate / wrong schema_version → DENY.
  2. expires_at — past the user-set expiry → PAUSE_FOR_REAUTH.
  3. halt_flag_set — kill switch tripped → DENY, no remote call.
  4. extract_order_intent — unparseable args → DENY.
  5. quantity → notional — quantity-only orders derive notional from a live quote; no quote → DENY (fail-closed). H3: if both quantity and notional_usd are present, the LARGER is enforced.
  6. check_mandate — exclude-list, instrument allowance, asset class, single notional cap, post-trade exposure cap, leverage cap, daily count, funding ceiling. Structural breaches → DENY; quantitative breaches → PAUSE_FOR_REAUTH.

The daily counter is incremented only on a confirmed ALLOW whose forwarded broker result is non-error (a failed forward never placed an order and never consumes a count, H2).

Every decision writes one redacted record to <runtime_root>/live/audit.jsonl.

Audit ledger

<runtime_root>/live/audit.jsonl is the unified live-action ledger. bybit-mcp writes here, Vibe-Trading's native guard writes here, and the CLI / SSE relay reads from here.

Each record (redacted via the redact_payload helper, so api_key, secret, *token*, account_number, routing_number, ssn, etc. are "[redacted]"):

{
  "audit_id": "la_3f2c…",
  "ts": "2026-06-02T09:15:49.482+00:00",
  "session_id": "vibe-2026-06-02-session-001",
  "kind": "order_placed",
  "outcome": "accepted",
  "server": "bybit",
  "remote_tool": "bybit_place_order",
  "intent_normalized": "buy $100 BTCUSDT (linear)",
  "mandate_snapshot_ref": "<sha256 of the mandate's consent token>",
  "consent_record_ref": "bybit-acct-0001",
  "broker_request": { "category": "linear", "symbol": "BTCUSDT", "side": "Buy", "qty": "0.001" },
  "broker_response": { "orderId": "1234567890", "orderLinkId": "" },
  "gate_decision": { "allowed": true, "decision": "allow", "checked_limits": [...] },
  "error": null
}

Kill switch

Stop all live activity instantly, independent of the LLM cooperating:

# Trip the GLOBAL switch (halts all brokers)
touch ~/.vibe-trading/live/HALT
# write the trip attribution
python -c "from bybit_mcp.safety.halt import trip_halt; trip_halt('cli', 'manual stop')"

# Trip a per-broker switch (halts bybit only)
python -c "from bybit_mcp.safety.halt import trip_halt; trip_halt('cli', 'manual stop', broker='bybit')"

# Clear (this is a privileged surface action, NOT exposed as an MCP tool)
python -c "from bybit_mcp.safety.halt import clear_halt; clear_halt()"
python -c "from bybit_mcp.safety.halt import clear_halt; clear_halt(broker='bybit')"

A user can also touch the sentinel directly (the by / reason JSON body is attribution only; the file's existence is what enforces the halt).

Integration with Vibe-Trading

Add an entry to your Vibe-Trading agent.json MCP registry:

{
  "mcpServers": {
    "bybit": {
      "command": "python",
      "args": ["-m", "bybit_mcp.server.mcp_server", "--transport", "stdio"],
      "env": {
        "VIBE_RUNTIME_ROOT": "~/.vibe-trading",
        "BYBIT_API_KEY": "${BYBIT_API_KEY}",
        "BYBIT_API_SECRET": "${BYBIT_API_SECRET}",
        "BYBIT_TESTNET": "false",
        "BYBIT_BROKER_KEY": "bybit"
      }
    }
  }
}

Vibe-Trading's native LiveOrderGuardTool (in-repo, wrapping the MCPRemoteTool instances) and the bybit-mcp server share:

  • The mandate file at <runtime_root>/live/<broker>/mandate.json.
  • The halt sentinel at <runtime_root>/live/HALT (and per-broker).
  • The daily counter at <runtime_root>/live/<broker>/trade_counter.json.
  • The audit ledger at <runtime_root>/live/audit.jsonl.

This means a single mandate commit and a single HALT sentinel covers both the in-process and the cross-process (MCP) order paths.

Environment

Var Default Purpose
BYBIT_API_KEY (empty = public-only) Bybit API key
BYBIT_API_SECRET (empty) Bybit API secret
BYBIT_TESTNET false Use testnet base URL
BYBIT_HTTP_TIMEOUT 10 httpx request timeout (seconds)
BYBIT_RECV_WINDOW 5000 recv_window for signed requests (ms)
BYBIT_BROKER_KEY bybit Per-broker mandate/halt/counter path key
VIBE_RUNTIME_ROOT ~/.vibe-trading Runtime root for mandate/audit/halt/counter
BYBIT_MCP_LOG_LEVEL INFO Logger level
BYBIT_MCP_HOST 127.0.0.1 HTTP host (when --transport http)
BYBIT_MCP_PORT 8001 HTTP port (when --transport http)

Architecture

+------------------------+        +-------------------------+
|   Vibe-Trading agent   | stdio  |   bybit-mcp process     |
|   - agent loop         |<------>|   - FastMCP server      |
|   - native guard (in-  |  HTTP  |   - MandateGate         |
|     process MCP tools) |        |   - 17 tools (11R+6W)   |
+-----------+------------+        +------------+------------+
            |                                  |
            |   <runtime_root>/live/...        |
            +----------------------------------+
                  shared state, audit ledger

The MCP server is stateless across tool calls — the mandate / halt / counter are re-read from disk on every invocation, so a user-side commit / trip is immediately visible to the next order attempt. The shared BybitClient keeps a single TCP connection open + a synced clock across the server lifetime (see server_lifespan in mcp_server.py).

Development

make install       # pip install -e ".[dev]"
make test          # pytest (95 tests, ~2s)
make lint          # ruff check
make format        # black + ruff --fix
make run-stdio     # stdio transport
make run-sse       # HTTP transport on :8001

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bybit_go_mcp-0.1.0.tar.gz (46.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bybit_go_mcp-0.1.0-py3-none-any.whl (46.1 kB view details)

Uploaded Python 3

File details

Details for the file bybit_go_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: bybit_go_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 46.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for bybit_go_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4de77c9b8e71fe951de20a2caca863b9309bc34153646f4170affce88d38d8d0
MD5 190fc90a268e78cd94d61e0bf9abc492
BLAKE2b-256 31744f9a0743da19fe1b2fbef72183f6bd87c26934871d94ca9b53439dd5f484

See more details on using hashes here.

File details

Details for the file bybit_go_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bybit_go_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 46.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for bybit_go_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 16715c9681c6afc2b8496a7f595736e0571ed63aeef69c6e96bbb32419ec0998
MD5 1a9a761552e45050f1a202b6b8c2b1a3
BLAKE2b-256 38ac0be493f4ab0830cd034c1d0a88bb5627df03dd4c05d86c9068795a3c23ca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page