Documentation

MCP Server (Live)

Connect an AI agent directly to the Live Execution API via the Model Context Protocol — manage live subscriptions and receive signed webhooks, no custom integration code required.

#Overview

emidlabs-live-mcp is a hosted Model Context Protocol (MCP) server that wraps the Live Execution API as agent-callable tools — the live counterpart to emidlabs-backtest-mcp. Point any MCP-compatible client or coding agent at it and it can create and manage live subscriptions and read emitted signals directly, without you writing a REST integration.

Endpoint: https://mcp.live.emidlabs.com/mcp (Streamable HTTP transport).

This server is a thin protocol adapter — it has no logic of its own beyond validating input and forwarding to live.emidlabs.com. Every subscription it creates still uses your own EmidLabs API key and counts against your account's own concurrent-subscription limit, the same as calling the REST API directly.

#Connecting an agent

Works withClaudeChatGPTCursor+ any MCP client
1

Pick your client

Claude and ChatGPT discover this server automatically over OAuth. Cursor and most coding agents instead want the raw JSON config block below.

2

Add the connector

Add the server to your MCP client's configuration. The exact file differs by client, but the shape is the same everywhere — a name and a URL:

mcp config (e.g. claude_desktop_config.json)
{
  "mcpServers": {
    "emidlabs-live": {
      "url": "https://mcp.live.emidlabs.com/mcp"
    }
  }
}
Some clients (e.g. Claude's "Add custom connector" dialog) ask for a plain URL instead of this JSON block. Paste the full address including the /mcp path — https://mcp.live.emidlabs.com/mcp, not just mcp.live.emidlabs.com. Dropping the path is the most common reason a connector fails to register, and the resulting error rarely makes the real cause obvious.
3

Authenticate

Three ways to authenticate, and you can combine more than one — the connector tries them in the order below:

MethodHowBest for
OAuth login (recommended)Just add the connector with no extra config. Claude/ChatGPT discover the login flow automatically and prompt you to sign in with your EmidLabs account — a dedicated API key is created for your account the first time and reused after that.Personal Claude.ai / ChatGPT connectors. Nothing to copy, paste, or type — ever.
x-api-key headerConfigure a specific EmidLabs API key once when adding the connector (e.g. Claude’s "custom header" / static_headers option) — attached automatically to every request from then on.Org-managed connectors where an admin wants every member sharing one specific key instead of individual OAuth logins.
Tool argumentPass an API key as liveApiKey on each call — the agent is told the key directly (e.g. in conversation).Coding agents, local/CLI use, or any client without connector-level auth support.
Precedence when more than one is present: header, then OAuth, then the argument — if none apply, every tool call fails fast with a clear error naming all three options, never a silent or ambiguous auth failure. Signing in via OAuth automatically grants the same underlying key the Backtest MCP server already uses (or creates one, the first time) — one login covers both connectors, no separate registration step.

#Tools

create_subscription

Subscribes a strategy to live evaluation on one asset pair. The strategy re-evaluates every time a candle closes on its own configured timeframe, emitting an Entry or Exit signal whenever decision.entry/decision.exit turns true — no position tracking, signal-only.

provider listens directly onCoinbaseBinance

No fixed asset list to consult first — any pair the chosen exchange actually lists can be subscribed. That's broader than submit_backtest, which only covers assets EmidLabs has already extracted and stored historical candles for.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
assetPairstringMust be in the exact format the chosen provider expects — no translation happens anywhere. Coinbase: "BTC-USDC" (hyphenated). Binance: "BTCUSDC" (no hyphen).
providerstring (optional)Which market data provider to listen on: "coinbase" or "binance". Defaults to "coinbase" when omitted.
strategySnapshotJsonobjectThe Strategy DSL object — see the strategy-dsl-spec resource below. Identical shape to submit_backtest.
webhookUrlstring (optional)Absolute https:// URL. When set, every emitted signal is also POSTed here — see Webhooks below.
modelstring (optional)"stateless" (default when omitted) or "stateful" — see Signal Model below.
isPositionedboolean (optional)Only meaningful when model is "stateful". Defaults to false (not positioned yet) when omitted.
liveBaseUrlstring (optional)Defaults to the public production API.

There's no check that assetPairactually exists on the chosen provider at creation time — an invalid pair, or the right pair in the wrong provider's format, is accepted immediately and then the subscription's status moves to "Errored" within about a minute. Check get_subscriptionshortly after creating if you're not sure the format was right.

webhookSecret is only ever returned in this one response — save it immediately. No tool or endpoint echoes it back later, the same one-time-reveal rule EmidLabs API keys already follow.

create_subscriptions_batch

Subscribes the SAME strategy to live evaluation on multiple asset pairs at once, one subscription per entry in assetPairs. Best-effort per item — one bad asset pair never fails the rest of the batch, check each item's own status/error.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
assetPairsstring[]One entry per subscription to create. Max 50 per call.
strategySnapshotJsonobjectShared by every subscription in this batch — identical shape to create_subscription's own field.
providerstring (optional)Shared by every subscription in this batch. Defaults to "coinbase" when omitted.
webhookUrlstring (optional)Shared by every subscription in this batch — each still gets its own signed deliveries and its own secret.
modelstring (optional)Shared by every subscription in this batch. "stateless" (default when omitted) or "stateful" — see Signal Model below.
isPositionedboolean (optional)Shared by every subscription in this batch, only meaningful when model is "stateful". Defaults to false when omitted.
liveBaseUrlstring (optional)Defaults to the public production API.
Each successful item gets its own webhookSecret when webhookUrl is set — returned once per item in this response, same one-time-reveal rule as create_subscription.

stop_subscription

Stops a live subscription. Idempotent — stopping an already-stopped subscription just returns its current status.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
subscriptionIdstringThe id returned by create_subscription.
liveBaseUrlstring (optional)Defaults to the public production API.

stop_subscriptions

Stops a specific list of live subscriptions, in one call. Idempotent per id — an already-stopped subscription is simply skipped, not an error.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
idsstring[]Subscription ids to stop, as returned by create_subscription/list_subscriptions. Max 200 per call.
liveBaseUrlstring (optional)Defaults to the public production API.

stop_all_subscriptions

Stops every currently active live subscription on this account, in one call. Idempotent — already-stopped subscriptions are simply skipped.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
liveBaseUrlstring (optional)Defaults to the public production API.
No way to scope this to a subset of your subscriptions — it acts on the whole account. Use stop_subscriptions with an explicit id list if you only want some stopped.

update_subscription

Updates a subscription's model/isPositioned/webhookUrl. assetPair, timeframe, provider, and the strategy itself can never be changed after creation — stop_subscription and create_subscription for that.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
subscriptionIdstringThe id returned by create_subscription.
modelstring (optional)"stateless" or "stateful". Omit to leave unchanged — see Signal Model below.
isPositionedboolean (optional)Only meaningful when this subscription's model is (or is being set to) "stateful". Omit to leave unchanged.
webhookUrlstring (optional)A new absolute https:// URL, or an empty string to remove the existing webhook. Omit to leave unchanged.
liveBaseUrlstring (optional)Defaults to the public production API.
Changing webhookUrl to a non-empty value generates a fresh webhookSecret, returned once in this response only — save it immediately.

get_subscription

Fetches a live subscription's current status by id — asset pair, provider, timeframe, status, model/isPositioned, and the last candle it evaluated.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
subscriptionIdstringThe id returned by create_subscription.
liveBaseUrlstring (optional)Defaults to the public production API.

list_subscriptions

Lists live subscriptions on this account, paginated.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
pageinteger (optional)Page number, starting at 1. Defaults to 1.
pageSizeinteger (optional)Results per page, 1-100. Defaults to 20.
statusstring (optional)Exact-match filter: "Active", "Stopped", or "Errored". Omit to return every status.
liveBaseUrlstring (optional)Defaults to the public production API.

get_subscription_signals

Lists Entry/Exit signals a live subscription has emitted, paginated, each with the candle it fired on, which named conditions were true, the score breakdown, and — for Entry signals — the computed stop-loss/take-profit price levels.

ArgumentTypeDescription
liveApiKeystring (optional)Not needed if you connected via OAuth login or the x-api-key header is configured on this connector.
subscriptionIdstringThe id returned by create_subscription.
pageinteger (optional)Page number, starting at 1. Defaults to 1.
pageSizeinteger (optional)Results per page, 1-100. Defaults to 20.
typestring (optional)Exact-match filter: "Entry" or "Exit". Omit to return both.
liveBaseUrlstring (optional)Defaults to the public production API.

#Signal Model: Stateless vs Stateful

Every subscription starts "stateless" — a signal is recorded/delivered every time decision.entry/decision.exitevaluates true, even repeatedly, if the underlying condition isn't edge-triggered (e.g. a plain rsi14 > 55 instead of crossUp(...)/crossDown(...) — it stays true for many consecutive candles). Switch a subscription to "stateful" via update_subscription to have it track its own Entry/Exit position internally (never queried from your exchange) and only fire on an actual transition — everything else is silently suppressed.

isPositioned is a manual correction, never inferred from a real exchange balance — this server has no broker integration of its own. Fix drift yourself via update_subscription when it happens (e.g. a trade placed manually outside this system).

Full behavior table: Live Execution API — Signal Model.

#Webhooks

Pass webhookUrl to create_subscription and every signal that subscription emits is also POSTed there as JSON — no polling required. Each delivery is signed so you can verify it really came from EmidLabs:

HeaderFormatVerification
X-Emidlabs-Signaturet={unix timestamp},v1={hex HMAC-SHA256}Recompute HMAC-SHA256(webhookSecret, `${timestamp}.${rawBody}`) and compare to v1 — same scheme EmidLabs itself uses to verify inbound Stripe webhooks, applied here on the sending side.

Delivery retries up to 3 times (short backoff) on network errors, timeouts, 429s, and 5xx responses from your endpoint — a slow or unreachable webhook never blocks or delays signal evaluation itself.

#Resources

strategy-dsl-spec

A condensed, markdown reference for the exact Strategy DSL JSON shape expected by create_subscription's strategySnapshotJson field (emidlabs://strategy-dsl-spec) — identical shape to emidlabs-backtest-mcp's own copy and the Copy Spec for AI block on the Strategy System page.

The inputs/conditions/score section is fetched live from emidlabs-strategy-mcp's domain owner (strategy-api's own GET /dsl-core-reference) and cached for a few minutes, rather than hand-copied — the same core emidlabs-backtest-mcp and emidlabs-strategy-mcp fetch too, so it can't drift out of sync between them. This server's own local part deliberately omits entryFeePct/exitFeePct (backtest-only), and documents riskManagement as advisory-only — a live subscription never auto-closes anything, unlike a backtest.

Reading this resource first is optional, not required — every field in create_subscription's own tool schema already documents its exact shape directly, since MCP clients don't reliably fetch a separate resource before generating a call.

#Rate limits

Two independent limits apply, and they surface differently on purpose so they're never confused for each other:

LimitScopeWhat it protectsWhat you see
Live-api quota / concurrent-subscription limitPer API keyYour account’s real usage/billing limitA 4xx from live-api, passed through as an MCP tool error.
MCP infra guardPer IP/connectionThis MCP process itself, against runaway loops or malformed floodsA 429 with a message explicitly identifying it as the MCP layer’s own guard, not your account quota.
The MCP server does not enforce its own copy of your account's subscription limit — that would risk two counters disagreeing for the same call. It only adds a loose, generous IP-based guard to protect the server process itself.

#Scope

This server only exposes live subscription management — creating, stopping, inspecting subscriptions, and reading their signals. It does not expose any Console actions (creating or rotating API keys, changing billing plans, account management). Those remain human-only actions in the Console UI, deliberately kept out of what an autonomous agent can do unsupervised.