MCP·v0.1 · JSON-RPC 2.0 over HTTP POST

CO-DASH MCP

Connect AI trading agents and clients to CO-DASH through a single MCP route. Seven read-only tools surface portfolio state, PnL, NAV history, and canonical trade events. The same workspace API key gates every tool and the full REST surface.

EndpointPOST https://p-api.kvants.ai/mcp
AuthAuthorization: Bearer kv_live_… (or x-api-key)
Content-Typeapplication/json
Get a keyPOST https://p-api.kvants.ai/api/agents/register

#Overview

MCP (Model Context Protocol) lets an AI agent discover and call tools on a remote server through a JSON-RPC 2.0 envelope. CO-DASH implements the streamable-HTTP transport on a single POST route; the same workspace API key used for the REST API also gates MCP.

The MCP surface is intentionally read-only. Account creation, AI-provider credentials, and automations remain on the REST API and are documented below under External REST API.

#Quickstart

Provision a workspace, attach a data source, and call the first tool in five steps.

#1. Register an agent workspace

The registration endpoint is public and rate limited. It returns a kv_live_… key once. Store it server-side immediately — the plaintext is unrecoverable.

bash
curl -sS https://p-api.kvants.ai/api/agents/register \
  -H "content-type: application/json" \
  -d '{
    "name": "research-agent",
    "keyName": "research-agent-prod"
  }'

#2. Connect data sources

MCP reads what the REST API provisions. Attach a read-only exchange account before expecting portfolio tools to return data.

bash
curl -sS https://p-api.kvants.ai/api/accounts \
  -H "authorization: Bearer $KVANTS_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name":      "Main Binance",
    "venue":     "binance",
    "apiKey":    "$BINANCE_API_KEY",
    "apiSecret": "$BINANCE_API_SECRET"
  }'

#3. (Optional) Add AI-provider keys

Required only if the workspace uses model-backed CO-DASH features. Not needed for MCP read tools.

bash
curl -sS https://p-api.kvants.ai/api/settings/ai-keys \
  -H "authorization: Bearer $KVANTS_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "provider": "anthropic", "key": "$ANTHROPIC_API_KEY" }'

#4. Initialize and call your first tool

Run the protocol handshake, list scoped tools, then call one.

bash
curl -sS https://p-api.kvants.ai/mcp \
  -H "authorization: Bearer $KVANTS_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-11-25",
      "clientInfo": { "name": "my-agent", "version": "1.0.0" }
    }
  }'
bash
curl -sS https://p-api.kvants.ai/mcp \
  -H "authorization: Bearer $KVANTS_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": "tools", "method": "tools/list" }'
bash
curl -sS https://p-api.kvants.ai/mcp \
  -H "authorization: Bearer $KVANTS_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "pnl-7d",
    "method": "tools/call",
    "params": {
      "name": "get_pnl_by_symbol",
      "arguments": { "days": 7, "limit": 25 }
    }
  }'
Note

Newly connected accounts may return limited tool data until balances, trades, and events have been collected by the data-collector worker.

#Authentication

Every MCP and REST request carries the workspace API key in one of two headers. Both forms are equivalent; Authorization is preferred for clients that already speak Bearer auth.

HeaderValue
AuthorizationBearer kv_live_…
x-api-keykv_live_…

Bearer tokens that do not start with kv_live_ are treated as web-app JWTs, not external API keys. Keys can be additionally restricted by expiry, source-IP allowlist, and account allowlist — all enforced server-side.

#Scopes

ScopeGrants
workspace:readLegacy umbrella. All read families including MCP tool discovery.
workspace:read:summarylist_accounts, get_pnl_by_symbol, account/portfolio summaries.
workspace:read:liveget_portfolio_summary, get_nav_history, status, metrics.
workspace:read:rawget_trade_events, get_largest_loss_events, raw forensics.
workspace:read:exportsGET /api/export/*.
workspace:write:accountsPOST /api/accounts.
workspace:write:ai-keysPOST/DELETE /api/settings/ai-keys.
workspace:write:automations/api/automations/* CRUD + run.

#Client configuration

Any MCP client that speaks streamable HTTP and supports a custom Authorization header works without a transport adapter. Point it at https://p-api.kvants.ai/mcp and send the workspace key.

json
{
  "mcpServers": {
    "co-dash": {
      "url": "https://p-api.kvants.ai/mcp",
      "headers": {
        "Authorization": "Bearer kv_live_..."
      }
    }
  }
}

Both Authorization: Bearer kv_live_… and x-api-key: kv_live_… are accepted. Do not send both — when both are present, x-api-key wins.

#Protocol

MCP is JSON-RPC 2.0 over a single POST route. The server supports these methods:

MethodPurpose
initializeNegotiate the protocol version. Returns server capabilities and instructions.
pingLiveness check. Returns an empty object.
tools/listList tools the current key is allowed to call. Scope filtered server-side.
tools/callInvoke a tool by name with validated arguments.
resources/listReturns an empty list. CO-DASH does not expose MCP resources.
prompts/listReturns an empty list. CO-DASH does not expose MCP prompts.
notifications/*Any client notification (no id). Server responds with 202 No Content.

Supported protocol versions: 2025-11-25 (default), 2025-06-18, 2025-03-26, 2024-11-05. The server echoes whichever version the client requested if it's in the supported set.

#Tool output envelope

Every successful tools/call returns:

json
{
  "jsonrpc": "2.0",
  "id": <request id>,
  "result": {
    "content": [{ "type": "text", "text": "<json-stringified output>" }],
    "structuredContent": <object>,
    "isError": false
  }
}

Parse structuredContent for deterministic data — the content[].text member is a JSON-stringified mirror for model consumption. Tool-level validation errors come back as isError: true on the same envelope (not as a JSON-RPC error).

#MCP error codes

CodeMeaning
-32600Invalid JSON-RPC envelope
-32601Unsupported MCP method
-32602Invalid tool name or arguments
-32001API key lacks the required scope
isError: trueTool-level validation or business-rule failure (returned on the success envelope)

#Tools

Seven read-oriented tools. Each defines its own argument schema with zod, rejects unknown keys, and re-checks scope before reading data. A key with the broad workspace:read scope can call every tool; narrower keys only see matching tools in tools/list.

list_accounts

workspace:read:summary

Discover exchange accounts visible to the current API key. Call this first — the ids it returns are required by every account-scoped tool.

Arguments

None.

structuredContent
{
  "count": 1,
  "accounts": [
    {
      "id": "00000000-0000-4000-8000-000000000001",
      "name": "Main Binance",
      "venue": "binance",
      "denomination": "USD",
      "portfolioId": null,
      "tags": ["cta"],
      "isActive": true,
      "status": "connected",
      "lastBalanceUsd": 1234.56,
      "lastCheckedAt": "2026-05-15T00:00:00.000Z",
      "createdAt": "2026-05-01T00:00:00.000Z",
      "updatedAt": "2026-05-15T00:00:00.000Z"
    }
  ]
}

get_portfolio_summary

workspace:read:live

Latest stored NAV and exposure snapshot. Numbers are summed across all selected accounts. BTC/ETH prices are returned only when a single account is selected.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
structuredContent
{
  "timestamp": "2026-05-15T00:00:00.000Z",
  "accountCount": 3,
  "navUsd": 1234567.89,
  "grossExposureUsd": 2000000,
  "netExposureUsd": 500000,
  "longExposureUsd": 1250000,
  "shortExposureUsd": 750000,
  "futuresUpnlUsd": 4321,
  "navBtc": 24.5,
  "navEth": 380.2,
  "btcPrice": null,
  "ethPrice": null
}

get_nav_history

workspace:read:live

Stored NAV time series. Returned points are the most recent 1500 buckets when the source produces more; truncated: true signals when this happened.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
daysinteger30Lookback window in days. Min 1, max 365.
structuredContent
{
  "count": 720,
  "days": 30,
  "truncated": false,
  "accountIds": ["00000000-0000-4000-8000-000000000001"],
  "points": [
    {
      "timestamp": "2026-04-15T00:00:00.000Z",
      "nav": 1200000,
      "grossExposure": 1800000,
      "netExposure": 400000
    }
  ]
}

get_trading_activity_summary

workspace:read:raw

Database-aggregated notional, fees, realized PnL, and forced-close counts for a window. Reach for this before drilling into raw events.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
fromISO datetimenow - hoursInclusive lower bound.
toISO datetimenowInclusive upper bound.
hoursnumber24Used when from/to are omitted. Min 1, max 2160 (90 days).
symbolsstring[]Optional. Up to 100 symbols.
eventTypes("trade" | "closed_pnl" | "funding" | "liquidation" | "adl" | "system_close" | "unknown")[]Optional. Up to 7 enum values.
structuredContent
{
  "window": {
    "from": "2026-05-14T00:00:00.000Z",
    "to": "2026-05-15T00:00:00.000Z",
    "hours": 24,
    "days": 1
  },
  "filters": {
    "accountIds": ["00000000-0000-4000-8000-000000000001"],
    "symbols": [],
    "eventTypes": []
  },
  "totals": {
    "eventCount": 1240,
    "notionalUsd": 9800000,
    "feesUsd": 4200,
    "realizedPnlUsd": -8500,
    "fundingUsd": -120,
    "forcedCloseCount": 0
  },
  "byAccount": [ /* per-account totals */ ],
  "bySymbol": [ /* per-symbol totals */ ]
}

get_pnl_by_symbol

workspace:read:summary

Realized, funding, fee, and net PnL grouped by symbol over a window. truncated: true when more than limit symbols match.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
fromISO datetimenow - daysInclusive lower bound.
toISO datetimenowInclusive upper bound.
daysinteger30Used when from/to are omitted. Min 1, max 365.
symbolsstring[]Optional. Up to 100 symbols.
limitinteger100Min 1, max 2000.
structuredContent
{
  "count": 25,
  "totalSymbolCount": 25,
  "window": {
    "from": "2026-05-08T00:00:00.000Z",
    "to": "2026-05-15T00:00:00.000Z",
    "hours": 168,
    "days": 7
  },
  "rows": [
    {
      "symbol": "BTC-PERP",
      "accountIds": ["00000000-..."],
      "accountNames": ["Main Binance"],
      "realizedPnlUsd": 1230.45,
      "fundingUsd": -42.10,
      "feesUsd": 88.20,
      "netPnlUsd": 1100.15
    }
  ],
  "truncated": false
}

get_trade_events

workspace:read:raw

Paginated canonical events: trades, funding, closed-PnL, liquidations, ADL, system-close. Persist nextCursor between calls and pass it back as cursor to fetch the next page. The window is only resolved on the first call.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
fromISO datetimenow - 90dInclusive lower bound. Ignored when cursor is set.
toISO datetimenowInclusive upper bound. Ignored when cursor is set.
symbolsstring[]Optional. Up to 100 symbols.
eventTypes("trade" | "closed_pnl" | "funding" | "liquidation" | "adl" | "system_close" | "unknown")[]Optional. Up to 7 enum values.
limitinteger100Min 1, max 1000.
cursorstringOpaque cursor from a previous response's nextCursor.
structuredContent
{
  "count": 100,
  "nextCursor": "eyJsYXN0VHM...",
  "window": {
    "from": "2026-02-14T00:00:00.000Z",
    "to": "2026-05-15T00:00:00.000Z",
    "hours": 2160,
    "days": 90
  },
  "rows": [
    {
      "id": "evt_01HK...",
      "accountId": "00000000-...",
      "accountName": "Main Binance",
      "symbol": "BTC-PERP",
      "eventType": "trade",
      "occurredAt": "2026-05-15T00:00:00.000Z",
      "side": "buy",
      "qty": 0.25,
      "price": 65000,
      "notionalUsd": 16250,
      "realizedPnlUsd": 0,
      "feeUsd": 6.5
    }
  ],
  "provenance": { /* source tables, fallbacks, applied filters */ }
}

get_largest_loss_events

workspace:read:raw

Worst individual events ranked by netPnlImpactUsd over a window.

Arguments
NameTypeDefaultNotes
accountIdstringOptional. Single visible exchange account id.
accountIdsstring[]Optional. Up to 100 ids. When both are omitted, all visible accounts are queried.
fromISO datetimenow - daysInclusive lower bound.
toISO datetimenowInclusive upper bound.
daysinteger30Used when from/to are omitted. Min 1, max 365.
symbolsstring[]Optional. Up to 100 symbols.
limitinteger25Min 1, max 500.
structuredContent
{
  "count": 25,
  "window": {
    "from": "2026-04-15T00:00:00.000Z",
    "to": "2026-05-15T00:00:00.000Z",
    "hours": 720,
    "days": 30
  },
  "rows": [
    {
      "id": "evt_01HK...",
      "accountId": "00000000-...",
      "accountName": "Main Binance",
      "symbol": "ETH-PERP",
      "eventType": "liquidation",
      "occurredAt": "2026-04-20T03:12:00.000Z",
      "netPnlImpactUsd": -18500,
      "notionalUsd": 95000
    }
  ],
  "provenance": { /* ... */ }
}

#External REST API

The same workspace API key authenticates the full REST surface. REST writes provision resources (accounts, AI keys, automations); REST reads expose data MCP also covers, plus the full Grafana dataset and bulk exports.

#Conventions

Most successful responses use a uniform envelope. Errors always use the same shape with success: false. Treat the error string as human-readable; rely on the HTTP status for control flow.

json
// success
{ "success": true, "data": <endpoint-specific payload> }

// error
{ "success": false, "error": "human-readable message" }

Exceptions: GET /health/*, GET /api/status, and most GET /api/grafana/* routes return the payload at the top level (no wrapper). Inspect HTTP status first there.

  • Timestamps are ISO 8601 / RFC 3339 UTC strings. PnL-analysis endpoints also accept date-only strings like 2026-04-13.
  • Money values are numbers, not strings. USD unless a *Btc / *Eth field name says otherwise.
  • Percent fields are percentages, not fractions. 8.32 means 8.32%, not 0.0832.
  • Forensics endpoints echo the resolved window in data.window. When from/to are omitted, the server substitutes a 90-day default — inspect window.defaulted before narrating per-symbol detail.
  • Cursor-paginated endpoints (/api/trade-events, /api/forensics/forced-events) skip the default window on subsequent pages and return window: null.

#Read endpoints

Grouped by scope family. Grafana read routes (GET /api/grafana/*) are also API-key accessible under the relevant read scope and back the same datasets the in-app dashboard renders.

workspace:read:summarysummary
  • GET /api/accounts
    Account ids, labels, status
  • GET /api/portfolios
    Portfolio groups (tree=true for nested)
  • GET /api/portfolio/report
    One-call agent report
  • GET /api/pnl-analysis/summary
    Windowed PnL summary
  • GET /api/forensics/account-summary
    Postmortem-style summary
  • GET /api/forensics/pnl-by-symbol
    Symbol-level PnL
  • GET /api/forensics/pnl-buckets
    PnL by hour / weekday / venue
workspace:read:livelive
  • GET /api/portfolio
    Live workspace snapshot
  • GET /api/portfolio/history
    NAV and exposure history
  • GET /api/portfolio/exposure-summary
    Latest exposure & position-risk
  • GET /api/status
    Exchange connectivity status
  • GET /api/metrics
    Rolling workspace performance
workspace:read:rawraw
  • GET /api/trades
    Raw trade fills, newest first
  • GET /api/trade-events
    Canonical events, cursor pagination
  • GET /api/trade-events/activity-summary
    All-account activity rollup
  • GET /api/forensics/forced-events
    Liquidations / ADL / system close
  • GET /api/forensics/largest-loss-events
    Worst events by impact
  • GET /api/forensics/risk-timeline
    Position risk over a window
  • POST /api/pnl-analysis/query
    Grouped / pivoted PnL rows
workspace:read:exportsexports
  • GET /api/export/counts
    Row counts per exportable table
  • GET /api/export/:table/count
    Row count for one table
  • GET /api/export/:table
    csv / json / jsonl, gzip optional

#Write endpoints

EndpointPurposeAuth
POST /api/agents/registerCreate a workspace + one-time API keypublic, rate limited
POST /api/accountsAdd an exchange or wallet connectionworkspace:write:accounts
POST /api/settings/ai-keysAdd or replace an AI-provider keyworkspace:write:ai-keys
DELETE /api/settings/ai-keys/:providerRemove an AI-provider keyworkspace:write:ai-keys
POST/PATCH/DELETE /api/automationsManage scheduled runsworkspace:write:automations
POST /api/automations/:id/runFire a run on demandworkspace:write:automations
POST /api/automations/trigger/:tokenInbound webhookpublic, token-gated

#HTTP errors

StatusMeaning
400Invalid query string or request body
401Missing, invalid, revoked, or expired API key
403Blocked by scope, IP allowlist, or account visibility
404Resource not found
405Route does not accept API key authentication
429Rate limit exceeded — honor Retry-After
504Request exceeded the server-side deadline
500Internal server error — retry once, then escalate

#Rate limits

Two one-minute sliding-window limits apply to API-key traffic:

  • Credential attempt: 300 requests / minute / caller IP on any request that carries an API-key credential (valid or invalid). Coarse IP backstop.
  • Read usage: 100 requests / minute / API key hash on authenticated reads. Current usage is surfaced in X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset response headers.

When limited, the response is 429 with a Retry-After header. Honor it — don't tight-loop.

#Operations

Treat MCP as a financial data boundary. The route uses the same expiration, IP allowlist, account allowlist, and rate-limit middleware as the external API.

  • Keep kv_live_ keys out of prompts, logs, browser storage, and client-side bundles.
  • Use account allowlists when a user or agent should only inspect a subset of exchange accounts.
  • Prefer summary or live scopes unless the agent truly needs raw trade events.
  • Treat every MCP response as data: parse structuredContent, validate ranges, and keep evidence links in your own audit trail.
  • Set timeouts around tool calls. Retry only idempotent reads with bounded backoff.
  • Rotate the workspace key when a user, agent, tenant, or execution environment is retired.