MCP Integration
Balchemy exposes its full capability layer through the Model Context Protocol (MCP). Both human-operated clients (Claude Desktop, OpenAI, etc.) and external AI agents can call the same execution core, subject to scope and policy rules.
MCP is the protocol surface only — it does not create a separate business-logic layer. Every tool call flows through the same policy, audit, and trading engine that the rest of the platform uses.
Quick start — Claude Desktop in 30 seconds
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"balchemy": {
"transport": "http",
"url": "https://api.balchemy.ai/mcp/<your-publicId>",
"headers": {
"Authorization": "Bearer <your-mcp-api-key>"
}
}
}
}Replace <your-publicId> and <your-mcp-api-key> with values from your bot's MCP settings in Studio. Restart Claude Desktop and the ask_bot and trade_command tools will appear in the tool list.
Endpoints
ALL https://api.balchemy.ai/mcp/:publicId
GET https://api.balchemy.ai/mcp/:publicId/events/sse
The main endpoint accepts any HTTP method — POST is standard for JSON-RPC calls. The SSE endpoint provides a streaming event channel for real-time responses.
The mcp prefix is intentionally outside the /api namespace. This keeps the MCP surface clean and separately addressable.
Two MCP modes
Balchemy supports two distinct principal models over the same endpoint infrastructure.
Mode 1 — Studio bot MCP (human operator)
Use this when a real person connects Balchemy tools from Claude Desktop, the OpenAI Plugins interface, or another MCP-compatible client.
The operator:
- owns or manages a Studio bot
- creates an MCP API key in the bot cockpit (
Studio > Bot > MCP) - connects that key from their client application
- all requests execute in that bot's principal context
Authentication is via a long-lived MCP API key scoped to the bot. The key is revealed once at creation time and stored as a hash — if you lose it, rotate it.
Mode 2 — External agent MCP (Hub/ERC-8004)
Use this when an external AI agent discovers Balchemy through ERC-8004 identity onboarding.
The external agent:
- onboards through
/api/public/erc8004/onboarding/siweor/identity - receives a
publicIdand identity access token - calls tools via
POST /mcp/<publicId>using the identity token as the bearer token - executes in its own isolated principal context with its own custodial wallet
See ERC-8004 Agent Identity for the full onboarding flow.
Authentication
Studio bot — MCP API key
POST /mcp/<publicId>
Authorization: Bearer <mcp-api-key>
Content-Type: application/jsonKeys are managed through the bot MCP interface:
| Method | Path | Description |
|---|---|---|
GET | /api/nest/bots/:botId/mcp | Get MCP config and key list |
POST | /api/nest/bots/:botId/mcp/keys | Create a new MCP API key |
DELETE | /api/nest/bots/:botId/mcp/keys/:keyId | Revoke a key |
POST | /api/nest/bots/:botId/mcp/step-up | Issue a step-up token |
GET | /api/nest/bots/:botId/mcp/logs | Fetch audit logs |
External agent — identity access token
POST /mcp/<publicId>
Authorization: Bearer <identity-access-token>
Content-Type: application/jsonIdentity access tokens are issued by AgentIdentityIssuerService using ES256 (ECDSA P-256). TTLs are scope-capped: read tokens last up to 3600 s, trade tokens up to 300 s, manage tokens up to 60 s.
You can also pass the token via the X-Balchemy-Mcp-Key or X-Mcp-Token headers as alternatives to Authorization.
Scope model
All MCP keys and identity tokens carry a scope that limits which tools they can call.
| Scope | Access | Safety profile |
|---|---|---|
read | Market data, portfolio, research, logs, status, read-only config | read_only |
trade | All read access plus trading commands, approvals, mutable workflows | trade_guarded |
manage | Control-plane actions: key rotation, scope updates, withdraw ownership | manage_step_up |
Scope hierarchy for external agents
External agents that onboard automatically receive read + trade by default. The manage scope is not granted at onboarding time. A developer must claim the agent's control plane (via Hub), then issue a step-up token before manage actions are permitted.
Step-up for manage actions
Manage-scoped operations require an additional step-up header:
X-Balchemy-Step-Up: <step-up-token>Studio bot:
POST /api/nest/bots/:botId/mcp/step-up
Authorization: Bearer <session-token>External agent:
POST /api/nest/agents/:agentId/control/mcp/step-up
Authorization: Bearer <identity-access-token>
X-Balchemy-Step-Up: <existing-step-up-or-manage-token>If the X-Balchemy-Step-Up header is missing or expired, manage requests return 403 Forbidden.
Tool model
The platform has 106 registered tools in a single ToolRegistry singleton. Both MCP and LLM function calling consume tools from the same registry — there is no separate MCP tool universe.
Default tool set (7 tools)
When MCP_EXPOSE_GRANULAR_TOOLS is false (the default), only high-level agent-facing tools are visible over MCP:
| Tool | Scope | Description |
|---|---|---|
ask_bot | trade | Send a natural-language message to the bot and receive a reply |
trade_command | trade | Execute a natural-language trading command through the trading engine |
agent_execute | trade | High-level execution for external agents — runs intent through the core pipeline |
agent_research | read | Research a token or market query; returns a structured research envelope with delta tracking |
agent_portfolio | read | Portfolio and position snapshot with delta-from-last-call tracking |
agent_status | read | Auth, scope, and runtime health status for the calling agent |
agent_config | trade | Get or update trading defaults and risk policy (get / update_trade_defaults / update_risk_policy) |
This default keeps the tool list concise and low-noise for agents that only need high-level orchestration.
Full tool set (106 tools, MCP_EXPOSE_GRANULAR_TOOLS=true)
Set the environment variable on the backend to expose all registered tools over MCP:
MCP_EXPOSE_GRANULAR_TOOLS=trueTool categories and approximate counts:
| Category | Approx. count | Example tools |
|---|---|---|
ai | 7 | ask_bot, agent_execute, agent_research, agent_portfolio, agent_status, agent_config, trade_command |
trading-core | 8+ | trading_positions, trading_orders, trading_swap_solana, trading_swap_evm, trading_dca |
trading-strategies | 6+ | Strategy CRUD, strategy execution |
trading-config | 5+ | Trading defaults, risk policy, approval config |
wallet | 14 | trading_wallet_list, trading_wallet_default, trading_wallet_connect, trading_wallet_verification_message, approvals, default order profiles |
evm | 6+ | EVM-specific positions, balances, approvals |
evm-dex | 6 | DEX pool data, liquidity, pool search |
evm-pretrade | 4+ | Pre-trade approval checks, allowance queries |
market | 14 | Token info, price, OHLCV, trending, top movers, market cap |
security | 3 | Contract security scan, rugpull check, holder analysis |
solana | 8+ | Solana-specific token data, Jupiter routing, Solana balances |
indexer | 5 | On-chain indexer queries for transactions, holders |
social | 1 | X/Twitter sentiment and post search |
launchpad | 9 | Pump.fun / launchpad token creation, bonding curve data |
verification | 3+ | Identity verification helpers |
simulation | 2 | Trade simulation, slippage estimation |
Scope still applies regardless of MCP_EXPOSE_GRANULAR_TOOLS. A read-scoped key cannot call trade-guarded tools even if all tools are listed.
Request/response examples
Standard tool call (JSON-RPC 2.0)
POST /mcp/<publicId>
Authorization: Bearer <mcp-api-key>
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "tools/call",
"params": {
"name": "ask_bot",
"arguments": {
"message": "What is my current SOL balance and any open positions?",
"chat_id": "session-abc123"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"content": [
{
"type": "text",
"text": "{\"reply\":\"Your SOL balance is 4.82 SOL...\",\"structured\":{\"compacted_count\":0},\"trace_id\":\"...\",\"session_cursor\":\"2026-03-19T10:00:00.000Z\",\"capabilities_hash\":\"...\",\"metadata\":{\"tool\":\"ask_bot\",\"raw_mode\":false,\"ts\":\"...\"}}"
}
]
}
}The text field always contains a JSON string with the reply (human-readable), structured (machine-readable), and metadata (trace/session) keys. Parse it with JSON.parse.
External agent — research call
POST /mcp/<publicId>
Authorization: Bearer <identity-access-token>
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "req-002",
"method": "tools/call",
"params": {
"name": "agent_research",
"arguments": {
"query": "BONK token recent activity",
"chain": "solana",
"includeOnchain": true,
"includeHolders": false
}
}
}List available tools
POST /mcp/<publicId>
Authorization: Bearer <mcp-api-key>
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "req-003",
"method": "tools/list"
}Response includes the tool names, descriptions, and JSON Schema input schemas for all scope-permitted tools.
SSE streaming
GET /mcp/<publicId>/events/sse
Authorization: Bearer <mcp-api-key>
Accept: text/event-streamThe SSE channel delivers real-time events as the tool execution progresses. Each event follows the standard data: <json> format.
Tool parameters reference
ask_bot
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Natural-language message to send to the bot |
chat_id | string | No | Conversation session ID for context continuity; defaults to mcp-<botId> |
metadata | object | No | Arbitrary key-value metadata passed to the message context |
trade_command
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Natural-language trading command |
chat_id | string | No | Conversation session ID |
last_mentioned_ca | string | No | Contract address most recently mentioned in conversation context |
recent_messages | string[] | No | Recent conversation messages for context window |
agent_research
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Research query (token symbol, address, or topic) |
chain | string | No | Target chain: solana, base, or ethereum |
includeX | boolean | No | Include X/Twitter posts |
includeOnchain | boolean | No | Include on-chain activity data |
includeDevWallets | boolean | No | Include developer wallet analysis |
includeHolders | boolean | No | Include holder distribution data |
maxPosts | number | No | Maximum number of social posts to include |
agent_config
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | One of: get, update_trade_defaults, update_risk_policy |
defaults | object | Conditional | Required for update_trade_defaults — trading default overrides |
policy | object | Conditional | Required for update_risk_policy — risk policy updates |
Error codes
| Code | Meaning | Common cause |
|---|---|---|
401 Unauthorized | Missing or invalid bearer token | Key revoked, expired identity token |
403 Forbidden | Scope insufficient or step-up missing | trade tool called with read key; manage action without step-up |
404 Not Found | publicId not found | Wrong public ID or agent not yet provisioned |
429 Too Many Requests | Rate limit exceeded | Too many calls in the configured window |
400 Bad Request | Invalid JSON-RPC request body | Malformed JSON or missing required fields |
JSON-RPC -32601 | Method not found | Calling an unknown method value |
JSON-RPC -32603 | Internal error | Tool execution error; see message field |
JSON-RPC INVALID_PARAMS | Bad tool arguments | Missing required tool argument |
JSON-RPC INVALID_REQUEST | Missing principal context | Bot user context could not be resolved |
Rate limits
MCP rate limiting is configurable server-side via environment variables:
| Variable | Default | Description |
|---|---|---|
MCP_RATE_LIMIT_ENABLED | false | Enable/disable rate limiting |
MCP_RATE_LIMIT_MAX | (unset) | Maximum requests per window |
MCP_RATE_LIMIT_WINDOW | (unset) | Window duration in milliseconds |
When rate limiting is enabled and a caller exceeds the limit, the server returns 429 Too Many Requests. The response body is a standard JSON-RPC error envelope.
In production deployments Balchemy also enforces Redis-backed per-user rate limits at the application layer independently of MCP-specific limits.
Audit and safety
Every MCP tool call is:
- Scope-checked against the key or token scope before the tool executes
- Policy-evaluated by the tool's
safetyProfile(read_only/trade_guarded/manage_step_up) - Origin-filtered against
MCP_ALLOWED_ORIGINSif set - Audit-logged to
McpAuditLog— toolName, scope, authType, keyId, ip, duration, success/failure - Argument-redacted in the log — wallet addresses, order IDs, and token mints are masked to
xxxx...xxxx - Session-tracked —
Bot.mcpConfig.lastUsedAtand per-keylastUsedAtare updated on each call
Audit logs are accessible at GET /api/nest/bots/:botId/mcp/logs.
Control-plane routes (external agents)
After an agent's control plane is claimed in Hub, these management routes become available:
POST /api/nest/agents/:agentId/control/claim
POST /api/nest/agents/:agentId/control/mcp/step-up
PUT /api/nest/agents/:agentId/control/scopes
POST /api/nest/agents/:agentId/control/mcp/keys/rotate
PUT /api/nest/agents/:agentId/control/withdrawAll control-plane routes require a manage-scoped token plus a valid X-Balchemy-Step-Up header.