/**
* Execute.run MCP Server
*
* Model Context Protocol server that exposes the Execute.run bot API.
* Allows AI agents to interact with Execute.run accounts via card API keys.
*
* Tools:
* - whoami: Get card and wallet identity
* - get_balance: Get current Shell balance and ceiling
* - get_transactions: Get transaction history
* - transfer: Transfer Shells to another wallet
* - sign: Sign a challenge with card's Ed25519 keypair
* - compute: Execute LLM request by burning Shells
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type Tool,
} from "@modelcontextprotocol/sdk/types.js";
// ============================================================================
// Configuration
// ============================================================================
const API_BASE_URL =
process.env.EXECUTE_RUN_API_URL || "https://execution.run/v1";
const API_KEY = process.env.EXECUTE_RUN_API_KEY;
// ============================================================================
// Types
// ============================================================================
interface ApiError {
error: {
code: string;
message: string;
};
}
interface WhoamiResponse {
cardId: string;
walletId: string;
address: string;
balance: number;
status: string;
}
interface BalanceResponse {
balance: number;
ceiling: number;
}
interface Transaction {
id: string;
type: string;
amount: number;
balanceAfter: number;
timestamp: unknown;
purpose?: string;
toAccountId?: string;
fromAccountId?: string;
taskRef?: string;
model?: string;
}
interface TransactionsResponse {
transactions: Transaction[];
}
interface TransferResponse {
transaction: Transaction;
}
interface SignResponse {
signature: string;
cardId: string;
walletId: string;
}
interface ComputeResponse {
content: string;
model: string;
cost: number;
}
// ============================================================================
// API Client
// ============================================================================
async function apiRequest<T>(
endpoint: string,
method: "GET" | "POST" = "GET",
body?: unknown,
): Promise<T> {
if (!API_KEY) {
throw new Error("EXECUTE_RUN_API_KEY environment variable is required");
}
const url = `${API_BASE_URL}${endpoint}`;
const options: RequestInit = {
method,
headers: {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
};
if (body && method === "POST") {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
const errorData = data as ApiError;
throw new Error(
`API Error: ${errorData.error?.code || "UNKNOWN"} - ${errorData.error?.message || "Unknown error"}`,
);
}
return data as T;
}
// ============================================================================
// Tool Definitions
// ============================================================================
const tools: Tool[] = [
{
name: "whoami",
description:
"Get your card and wallet identity. Returns cardId, walletId, address (cardName@walletId format), balance, and status.",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "get_balance",
description:
"Get the current Shell balance and ceiling for your wallet. Returns the available balance and maximum allowed balance.",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "get_transactions",
description:
"Get transaction history for your wallet. Returns a list of recent transactions including mints, burns, and transfers.",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description:
"Number of transactions to return (default: 50, max: 100)",
},
},
required: [],
},
},
{
name: "transfer",
description:
"Transfer Shells to another wallet. Accepts wallet ID (exe_xxx) or card address (cardName@exe_xxx). Requires the recipient, amount, and a purpose describing why the transfer is being made.",
inputSchema: {
type: "object",
properties: {
to: {
type: "string",
description:
"Recipient wallet ID (exe_xxx) or card address (cardName@exe_xxx)",
},
amount: {
type: "number",
description:
"Amount of Shells to transfer (must be a positive integer)",
},
purpose: {
type: "string",
description: "Purpose/context for the transfer (required)",
},
},
required: ["to", "amount", "purpose"],
},
},
{
name: "sign",
description:
"Sign a challenge with your card's Ed25519 keypair. Returns the signature, cardId, and walletId. Useful for proving your identity to other bots.",
inputSchema: {
type: "object",
properties: {
challenge: {
type: "string",
description: "Base64-encoded challenge to sign",
},
},
required: ["challenge"],
},
},
{
name: "compute",
description:
"Execute an LLM request by burning Shells. The cost is calculated based on the model and token usage. Returns the LLM response content and the cost in Shells.",
inputSchema: {
type: "object",
properties: {
model: {
type: "string",
description:
"Model identifier (e.g., 'gemini-2.0-flash', 'gpt-4o', 'claude-3-5-sonnet-latest')",
},
messages: {
type: "array",
description: "Conversation messages",
items: {
type: "object",
properties: {
role: {
type: "string",
enum: ["system", "user", "assistant"],
description: "Message role",
},
content: {
type: "string",
description: "Message content",
},
},
required: ["role", "content"],
},
},
temperature: {
type: "number",
description: "Sampling temperature (0-2, optional)",
},
maxTokens: {
type: "number",
description: "Maximum tokens to generate (optional)",
},
},
required: ["model", "messages"],
},
},
];
// ============================================================================
// Tool Handlers
// ============================================================================
async function handleWhoami(): Promise<string> {
const result = await apiRequest<WhoamiResponse>("/whoami");
return JSON.stringify(result, null, 2);
}
async function handleGetBalance(): Promise<string> {
const result = await apiRequest<BalanceResponse>("/balance");
return JSON.stringify(result, null, 2);
}
async function handleGetTransactions(args: {
limit?: number;
}): Promise<string> {
const queryParams = args.limit ? `?limit=${args.limit}` : "";
const result = await apiRequest<TransactionsResponse>(
`/transactions${queryParams}`,
);
return JSON.stringify(result, null, 2);
}
async function handleTransfer(args: {
to: string;
amount: number;
purpose: string;
}): Promise<string> {
const result = await apiRequest<TransferResponse>("/transfer", "POST", args);
return JSON.stringify(result, null, 2);
}
async function handleSign(args: { challenge: string }): Promise<string> {
const result = await apiRequest<SignResponse>("/sign", "POST", args);
return JSON.stringify(result, null, 2);
}
async function handleCompute(args: Record<string, unknown>): Promise<string> {
const result = await apiRequest<ComputeResponse>("/compute", "POST", {
request: args,
});
return JSON.stringify(result, null, 2);
}
// ============================================================================
// Server Setup
// ============================================================================
const server = new Server(
{
name: "execution-run-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: string;
switch (name) {
case "whoami":
result = await handleWhoami();
break;
case "get_balance":
result = await handleGetBalance();
break;
case "get_transactions":
result = await handleGetTransactions(args as { limit?: number });
break;
case "transfer":
result = await handleTransfer(
args as { to: string; amount: number; purpose: string },
);
break;
case "sign":
result = await handleSign(args as { challenge: string });
break;
case "compute":
result = await handleCompute(args as Record<string, unknown>);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return {
content: [{ type: "text", text: `Error: ${message}` }],
isError: true,
};
}
});
// ============================================================================
// Main
// ============================================================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Execute.run MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});