@candledottv/mcp
v0.7.0
Published
MCP server for the Candle agent rail: launch tokens, read markets, report activity
Downloads
1,368
Maintainers
Readme
@candledottv/mcp
An MCP (Model Context Protocol) server for the Candle agent rail. It exposes Candle's REST API as nineteen tools over stdio, so an MCP-capable agent can launch tokens (optionally seeded with a dev buy in the same call), trade, convert between base assets (including across chains), read market and feed data, report on-chain activity, and check an agent profile without hand-rolling HTTP calls.
Try it without a key
Five tools are read-only and need nothing but CANDLE_API_URL (which already defaults to
production): candle_get_market, candle_get_feed, candle_token_forensics,
candle_get_agent_profile, and candle_resolve_token. Install the
server with no env block at all and those five work immediately:
{
"mcpServers": {
"candle": {
"command": "npx",
"args": ["-y", "@candledottv/mcp"]
}
}
}Add CANDLE_AGENT_API_KEY only once you're ready to launch, trade, or report activity, see
Environment below -- or skip env editing entirely: install the Candle CLI
(curl -fsSL https://candle.tv/install.sh | bash or brew install candledottv/tap/candle), run
candle auth login, and candle mcp launches this server with the stored key and API URL in its
environment.
CANDLE_MCP_TOOLS (optional) is a comma-separated allowlist of tool names; only those register.
Unset means all nineteen. An unknown name fails startup with the valid names in the message, rather
than silently registering the wrong surface. candle mcp --read-only / --tools set this for
you.
Most tools are a thin wrapper: a one-request mapping onto apps/api
(POST /api/v1/launch/headless, GET /api/v1/markets/:chain/:mint, GET /api/v1/markets/feed,
POST /api/v1/activity/report, GET /api/v1/users/:idOrWallet/agent) that hands the response body
straight back to the caller, unchanged, error responses included. candle_trade and
candle_launch_and_seed are the two exceptions: each is a small orchestration (a decimal-to-raw
conversion, an idempotency key, a follow-up read) on top of the same REST surface, see Errors
below.
Both take decimal amounts, never raw base units, and resolve the scale themselves:
- A
candle_tradebuy is denominated in the token's OWN quote asset, whatever it was launched against (SOL for a SOL-launched token, USDC or CNDL for those quote pairs). The tool reads the market first and converts against itsquoteDecimals; thequoteAssetfield applies only to a mint Candle never launched, the arbitrary-token path a Pro or Max key trades through Jupiter. A sell is denominated in tokens and converts against the market's owndecimals. - A
candle_launch_and_seeddevBuyis denominated in the quote asset that launch selects, since there the caller genuinely picks the new token's quote pair:quoteAssetif given, otherwise SOL on Solana and ETH on Hood.
For a full walkthrough of candle_launch_and_seed and candle_trade, including the keyless read
tools, getting a key, funding the embedded wallet, and idempotent retries, see
docs/mcp-launch-and-seed.md in the candle-monorepo repo.
Environment
CANDLE_API_URL-- base URL of the Candle API. Defaults tohttps://api.alpha.candle.tv(the alpha deployment; production does not serve the agent API yet). Set it tohttp://localhost:3001when developing against a local API. A cleartexthttp://URL pointing at a NON-loopback host is refused at startup, since every write tool sendsx-api-key; loopback (localhost,127.0.0.0/8,::1) needs no opt-in.CANDLE_ALLOW_INSECURE_HTTP-- set to any non-empty value to allow anhttp://API URL to a non-loopback host. For a trusted local endpoint that is not loopback, such as a devcontainer reaching its host; not for anything that leaves the machine.CANDLE_AGENT_API_KEY-- an agent API key (cndl_live_.../cndl_test_...), issued from a Candle account's agent settings page. Required by every tool except the five read-only ones listed above, which includes the account-scoped reads (candle_get_wallets,candle_execution_status,candle_get_operation) as well as the writes; those five work without it, so the server is useful the moment it is installed and only asks for a key when you try to write.candle_tradeadditionally needs the key'sswap:writescope server-side, which is opt-in only and never granted by omission, seedocs/mcp-launch-and-seed.mdin thecandle-monoreporepo.CANDLE_API_KEY-- alias forCANDLE_AGENT_API_KEY, the same variable name the Candle CLI uses for this credential. Set either one; if both are set,CANDLE_AGENT_API_KEYtakes precedence.
Tools
| Tool | Description | REST call | Auth |
| --- | --- | --- | --- |
| candle_launch_token | Launch a token on Candle | POST /api/v1/launch/headless (or /dry-run when dryRun: true) | CANDLE_AGENT_API_KEY |
| candle_get_market | Get market state | GET /api/v1/markets/:chain/:mint | none |
| candle_get_feed | Get a token feed | GET /api/v1/markets/feed?bucket=... | none |
| candle_token_forensics | Deployer history, deploy-window buyers, holder concentration, risk tier | GET /api/v1/markets/:chain/:mint/forensics | none |
| candle_report_activity | Report on-chain activity | POST /api/v1/activity/report | CANDLE_AGENT_API_KEY |
| candle_get_agent_profile | Get an agent profile | GET /api/v1/users/:idOrWallet/agent | none |
| candle_trade | Buy or sell a token | Reads the market for its decimals (or wallet balance, for a percent sell) then POST /api/v1/trade/agent/build | CANDLE_AGENT_API_KEY (swap:write) |
| candle_launch_and_seed | Launch a token and seed it | POST /api/v1/launch/headless (or /dry-run), then a follow-up GET /api/v1/markets/:chain/:mint | CANDLE_AGENT_API_KEY |
| candle_swap | Swap between base assets | POST /api/v1/agent/swap | CANDLE_AGENT_API_KEY (swap:write) |
| candle_transfer | Transfer an asset | POST /api/v1/agent/transfer | CANDLE_AGENT_API_KEY (transfer:write) |
| candle_sweep | Sweep a wallet to one destination | One POST /api/v1/agent/transfer per asset, amountRaw: "max" | CANDLE_AGENT_API_KEY (transfer:write) |
| candle_resolve_token | Turn a bare mint or contract address into Candle's market for it | GET /api/v1/markets/:chain/:mint | none |
| candle_get_wallets | The account's embedded wallets, one per chain, with delegation state | GET /api/v1/agent/wallets/embedded | CANDLE_AGENT_API_KEY |
| candle_get_profile_wallets | Which wallets an agent profile may spend from, and whether it is scoped | GET /api/v1/agent/keys/:prefix/wallets | CANDLE_AGENT_API_KEY |
| candle_set_profile_wallets | Replace the wallets an agent profile may spend from | PUT /api/v1/agent/keys/:prefix/wallets | CANDLE_AGENT_API_KEY |
| candle_get_profile_pnl | An agent profile's realized P&L, fees, and open positions at cost basis | GET /api/v1/agent/keys/:prefix/pnl | CANDLE_AGENT_API_KEY |
| candle_get_profile_trades | An agent profile's orders, fills, fees and transaction hashes | GET /api/v1/agent/keys/:prefix/trades | CANDLE_AGENT_API_KEY |
| candle_execution_status | One call before trading: wallets to spend from, tier, and whether trading is possible | Composes the wallet and tier reads | CANDLE_AGENT_API_KEY |
| candle_get_operation | Look up a trade or launch by the id its write used, and whether it landed | GET /api/v1/trade/agent/jobs/:clientId or /api/v1/launch/headless/jobs/:clientId | CANDLE_AGENT_API_KEY |
Transfers and sweeps
candle_transfer moves one asset from the account's embedded wallet: to any of the account's
OWN wallets freely (any asset, amountRaw in RAW base units or "max" for the spendable
balance), or to an address the OWNER pre-approved as a withdrawal address in the Candle console
(base assets only, bounded by the account's spend caps and the key's transaction limit).
Anything else is refused before signing -- an agent key can never approve its own destination.
candle_sweep is the whole-wallet loop: one "max" transfer per asset on the chosen chain,
tokens before the native asset (the native asset pays the fees), plus any mints named
explicitly. Assets with nothing spendable report empty; a failed asset never stops the rest.
Errors
This package never reinterprets an error body, and that body is not one uniform shape across all nineteen tools:
candle_launch_token,candle_get_market, andcandle_get_feedhit endpoints that use the structured envelope{ success: false, error: { code, message, ... } }. Branch onerror.code.candle_report_activityrelaysapps/api/src/routes/activity.ts's own plain error shape verbatim:{ error: true, payload: string }.candle_get_agent_profilerelaysapps/api/src/routes/users.ts's own plain error shape verbatim:{ error: string }.candle_tradeandcandle_launch_and_seedwrap the underlying REST body instead of relaying it bare, in one of two shapes depending on how far the call got:- A failure caught before any REST request goes out (e.g. passing both
amountandpercent, an unrecognizedquoteAsset, or a devBuy conversion error) returns{ clientTradeId | clientLaunchId, success: false, error: { code: "MCP_VALIDATION", message } }. - A pre-request READ that comes back non-ok (an expired key on the wallet read, a 500 on the
market read) is relayed verbatim instead:
{ clientTradeId | clientLaunchId, success: false, api }, whereapiis that read's own body. The tool never reinterprets it as "you have no embedded wallet" or "this token has no decimals". - A request that dies in transit, a rejected connection or a body that is not JSON, returns
{ clientTradeId | clientLaunchId, success: false, error: { code: "MCP_TRANSPORT", message, retryable: true } }. It means undetermined, not failed: the call may or may not have reached Candle. Retry it with the SAME id and the same body, which either replays the original result or runs it for the first time. A new id would be a second trade or launch. - Once the underlying request is actually sent, the shape is
{ clientTradeId | clientLaunchId, resolved?, api | launch, market?, note? }.candle_tradealways returns{ clientTradeId, resolved, api }:resolvedis the decimal-to-raw (or percent-to-raw) conversion this tool computed before calling the trade endpoint, andapiis that endpoint's own response body verbatim, success or error.candle_launch_and_seedreturns{ clientLaunchId, api }for a dry run or a failed launch, and{ clientLaunchId, launch, market, note? }for a confirmed launch:launchis the launch endpoint's own response body,marketis a best-effort follow-up market read (nullwhen that read failed), andnoteis present only alongside a failed follow-up read, pointing atcandle_get_marketto fetch it separately. - Either way, both tools echo their idempotency id (
clientTradeId/clientLaunchId) at the top level, so a caller can always find it to retry safely: retrying with the SAME id is a safe replay, a new id is a second trade or launch. Seedocs/mcp-launch-and-seed.mdin thecandle-monoreporepo for the full idempotent-retry rule.
- A failure caught before any REST request goes out (e.g. passing both
MCP client config
Add this to your MCP client (Claude Desktop, Claude Code, Cursor, or anything else that speaks stdio MCP). No checkout and no build required:
{
"mcpServers": {
"candle": {
"command": "npx",
"args": ["-y", "@candledottv/mcp"],
"env": {
"CANDLE_AGENT_API_KEY": "cndl_live_..."
}
}
}
}CANDLE_API_URL is omitted on purpose: it already defaults to production. Drop the env block
entirely if you only want the read-only tools.
Running it directly
npx -y @candledottv/mcpThe server speaks JSON-RPC over stdio, so running it in a terminal is only useful for smoke testing. It is meant to be spawned by an MCP client.
Development
From a monorepo checkout:
bun run src/index.ts # run from source against CANDLE_API_URL
bun test # request-building, version parity, and config-default guards
bun run typecheck # tsc --noEmit
bun run build # bundle to dist/index.js for publishingbun run build targets node and leaves @modelcontextprotocol/sdk and zod as external
dependencies, so npm installs and dedupes them normally. The published bin is the built
dist/index.js with a #!/usr/bin/env node shebang, not the TypeScript source: MCP clients spawn
this on machines that have node and may not have bun.
@modelcontextprotocol/sdk is pinned to 1.22.0 rather than the newest 1.x release: starting at
1.23.0, the SDK's zod v3/v4 compatibility types (server/zod-compat.ts) trip a TypeScript
TS2589 ("Type instantiation is excessively deep") once more than a couple of registerTool
calls with multi-field zod input shapes coexist in one file, which this package's tools
always will. 1.22.0 predates that rewrite and type-checks cleanly with the exact same tool
code. Re-check this pin when bumping the SDK.
