MCP for AI agents
Let Claude, Cursor or any MCP client trade, launch, run tasks and read your PnL through your API key — one tool per API operation, nothing to install.
VortexDeployer is an MCP server (Model Context Protocol, the open standard AI assistants use to call tools). Point an MCP client at the endpoint with your API key and it gets one tool per API operation — the same buy, sell, launch, tasks, wallets and PnL calls as the REST API, with the same scopes and limits.
| What | Value |
|---|---|
| Endpoint | https://evm.vortexdeployer.com/evm/api/v1/mcp |
| Transport | Streamable HTTP, stateless — plain JSON answers, no session to keep |
| Auth | Authorization: Bearer vd_… on every request, exactly like REST — or a vdo_… OAuth access token bound to one (OAuth for connectors) |
| Tools | 84, generated from the OpenAPI document — never out of date |
Every tool runs as your account with real funds. Give an agent a key with only the scopes it needs, put maxSolPerTrade and dailySolCap on it, and keep launch off unless it launches. Start with a read-only key.
Connect#
Claude Code
claude mcp add --transport http vortex https://evm.vortexdeployer.com/evm/api/v1/mcp \
--header "Authorization: Bearer vd_…"Claude Desktop, Cursor, Windsurf and most others take a JSON entry:
{
"mcpServers": {
"vortex": {
"url": "https://evm.vortexdeployer.com/evm/api/v1/mcp",
"headers": { "Authorization": "Bearer vd_…" }
}
}
}Then ask: *"Which of my wallets hold SOL?"*, *"Dry-run a 0.1 SOL buy of <mint> from wallet 12"*, *"Pause every running task"*. The assistant picks the tool, fills the arguments and shows you the answer.
From your own code — any MCP SDK works; with the official TypeScript one:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(new URL('https://evm.vortexdeployer.com/evm/api/v1/mcp'), {
requestInit: { headers: { Authorization: 'Bearer vd_…' } },
}));
const { tools } = await client.listTools();
const me = await client.callTool({ name: 'me', arguments: {} });Claude.ai (custom connector) — claude.ai can't send a static bearer key, so it connects with OAuth instead: add a custom connector with the URL https://evm.vortexdeployer.com/evm/api/v1/mcp, and Claude runs the standard OAuth 2.1 dance against this server — register, consent, token. At consent you sign in here and pick which of your API keys the connector acts as; the access token it receives (vdo_…) then works everywhere a vd_ key does, governed by that key's scopes and caps. Full details: OAuth for connectors.
Tools#
Tool names are the operationIds of the OpenAPI document. A tool's arguments are the operation's path and query parameters plus its JSON body, flattened into one object; a route that takes an Idempotency-Key header takes it as the idempotencyKey argument.
| Tool | REST call | Scope |
|---|---|---|
me | GET /me | any |
buy, sell, trade_batch | POST /buy, /sell, /trade/batch | trade |
launch | POST /launch (dryRun defaults to true) | launch |
list_launchpads, launch_quotes | GET /launchpads, /launchpads/:launchpad/quotes — which quote tokens and stocks a launch can use | read |
list_wallets, wallet_balances, token_balances, wallet_profile | GET /wallets, /wallets/balances, /wallets/token-balances, /wallets/:id/profile | read |
create_wallets, update_wallet, delete_wallet | POST /wallets, PATCH/DELETE /wallets/:id | trade |
list_wallet_groups, create_wallet_group, rename_wallet_group, delete_wallet_group | /wallets/groups… | read / trade |
list_cashback, claim_cashback | GET /wallets/cashback, POST /wallets/cashback/claim (dryRun defaults to true) | read / trade |
portfolio, holdings, portfolio_pnl, daily_pnl, pnl, wallet_totals | GET /portfolio…, /pnl, POST /pnl/wallet-totals | read |
list_projects, project_by_id, project_by_mint, project_wallets | GET /projects… | read |
create_project, create_cto_project, update_project, delete_project, assign_project_wallets, unlink_project_wallet | POST/PATCH/DELETE /projects… | trade |
closeout_project | POST /projects/:id/closeout (dryRun defaults to true) | trade |
launch_readiness, next_mint_address, clone_token_look, launch_affordability, supply_preview, pons_launch_terms, otcdesks_rewards | GET/POST /launch/… helpers | read |
upload_metadata | POST /launch/metadata | launch |
list_supply_plans, save_supply_plan, delete_supply_plan, mount_supply_plan | /supply-plans… | read / trade |
conversion_assets, conversion_quote, convert | POST /convert/assets, /convert/quote, /convert | read / trade |
burn_tokens, lock_tokens | POST /tokens/burn, /tokens/lock (dryRun defaults to true) | trade |
creator_fees, claim_creator_fees | GET /tokens/creator-fees, POST /tokens/creator-fees/claim | read / trade |
list_events, warmup_candidates | GET /events, /warmup/candidates | read |
trading_settings, update_trading_settings | GET/PATCH /settings/trading | read / trade |
list_kols, track_kol, update_kol, untrack_kol | /kols… | read / trade |
list_tasks, get_task, task_executions, task_playbook, task_postmortem, task_mixer_orders, task_disperse, preview_task | GET /tasks…, POST /tasks/:id/preview | read |
create_task, update_task, delete_task, start_task, resume_task, pause_task, stop_task, stop_all_tasks | POST/PATCH/DELETE /tasks… | trade |
list_presets, save_preset, delete_preset | /tasks/presets… | read / trade |
execute_mixer_recipient | POST /tasks/:id/mixer-orders/:orderId/recipients/:recipientId/execute | trade |
Every tool carries MCP annotations: readOnlyHint is true on the GET tools, so a client that asks before any non-read call asks before every trade.
What a call returns#
A tool result is the REST answer, as text and as structuredContent, with the HTTP status and your rate window in _meta:
{
"content": [{ "type": "text", "text": "{ \"user\": { \"id\": \"1\", … }, \"key\": { … } }" }],
"structuredContent": { "user": { "id": "1", "username": "yourname" }, "key": { "name": "agent", "scopes": ["read"], "restricted": false } },
"_meta": { "status": 200, "rateLimit": { "limit": 300, "remaining": 299, "reset": 1758050400 } }
}A refusal is isError: true with the same body a REST caller gets — a stable code, the human error and a traceId — so the assistant can read insufficient_scope, validation_failed (with details) or rate_limited and act on it:
{ "isError": true, "content": [{ "type": "text", "text": "{ \"code\": \"insufficient_scope\", \"error\": \"this key lacks the trade scope\", \"traceId\": \"…\" }" }] }Scopes, per-key limits, rate limits, idempotency and the error codes are exactly the REST ones — each tool call is the REST call, made for you.
Good to know#
- No session. Every request authenticates on its own; there is nothing to resume and a dropped connection loses nothing.
- Long calls. A launch or a synchronous batch can take a while; a tool call waits up to three minutes for its answer.
- Streams are not tools. For live trades, PnL and balances use the WebSocket.
- Usage from an agent shows in the API hub under the same key, per route.