MCX (Modular Code Execution)
An MCP server that enables AI agents to execute sandboxed JavaScript and TypeScript code instead of calling individual tools directly. It significantly reduces token usage by allowing agents to filter, aggregate, and transform data locally before returning results.
README
MCX - Modular Code Execution
███╗ ███╗ ██████╗██╗ ██╗
████╗ ████║██╔════╝╚██╗██╔╝
██╔████╔██║██║ ╚███╔╝
██║╚██╔╝██║██║ ██╔██╗
██║ ╚═╝ ██║╚██████╗██╔╝ ██╗
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
MCP server that lets AI agents execute code instead of calling tools directly.
Based on Anthropic's code execution article.
The Problem
Traditional MCP has two inefficiencies:
-
Tool definition overload - Loading all tool definitions floods context. Thousands of tools = hundreds of thousands of tokens before any work begins.
-
Intermediate result bloat - Every API response passes through the model. A list of 100 records with nested data can consume 50,000+ tokens.
The Solution
Instead of calling tools directly, the agent writes code that runs in a sandbox:
// Agent writes this code, MCX executes it
const invoices = await api.getInvoices({ limit: 100 });
return {
count: invoices.length,
total: sum(invoices, 'amount'),
byStatus: count(invoices, 'status')
};
// Returns ~50 tokens instead of 50,000
Result: 98% token reduction by filtering data inside the execution environment.
Key Benefits
| Benefit | Description |
|---|---|
| Progressive Disclosure | Adapters loaded on demand, not upfront. Agent only sees what it needs. |
| Context Efficiency | Filtering, aggregation, and transformation happen in sandbox. Model sees results, not raw data. |
| Control Flow | Loops, conditionals, retries run as native code - no back-and-forth with model. |
| Privacy | Intermediate data stays in sandbox. Model only sees what code explicitly returns. |
| Skills | Save reusable operations as skills that combine multiple adapter calls. |
Installation
bun install
bun run build
Fully Bun-native: Uses Bun for package management, building, and runtime. No Node.js required.
Quick Start
1. Setup
# Create environment file
cp .env.template .env
# Create config file
cp mcx.config.template.ts mcx.config.ts
2. Create Adapters
# Option A: Generate from OpenAPI docs (recommended)
mcx gen
# Option B: Copy from template
cp adapters/adapter.template.ts adapters/my-api.ts
3. Run
# Start MCP server (stdio mode for Claude Code)
mcx serve
# Or just run mcx (serve is the default command)
mcx
Templates
| File | Description |
|---|---|
.env.template |
Environment variables (API keys, etc.) |
mcx.config.template.ts |
MCX configuration (adapters, sandbox settings) |
adapters/adapter.template.ts |
Adapter template with CRUD examples |
skills/skill.template.ts |
Skill template with 3 patterns |
CLI Commands
mcx serve
Start the MCP server. This is the default command when running mcx without arguments.
mcx serve [options]
Options:
-t, --transport <type> Transport mode: stdio (default) or http
-p, --port <number> HTTP port (default: 3100, only for http)
-c, --cwd <path> Working directory for config and adapters
Features:
- Auto-discovers
mcx.config.tsby walking up the directory tree - Loads
.envfiles automatically - HTTP transport binds to
127.0.0.1only (localhost) - HTTP mode exposes
/healthendpoint for monitoring
mcx gen
Generate adapters from OpenAPI specs. Run without arguments for interactive TUI mode.
# Interactive TUI (recommended)
mcx gen
# CLI mode - single file
mcx gen ./api-docs/users.md -n users
# CLI mode - batch directory
mcx gen ./api-docs -n myapi
# Filter specific endpoints
mcx gen ./api-docs -n myapi --include "invoices,payments"
mcx gen ./api-docs -n myapi --exclude "reports,audit"
Options:
-n, --name <name> Adapter name (auto-detected from source)
-o, --output <path> Output file (default: adapters/<name>.ts)
-b, --base-url <url> API base URL (auto-detected from OpenAPI)
-a, --auth <type> Auth type: basic, bearer, apikey, none
--read-only Generate GET methods only
--include <patterns> Include only matching endpoints (comma-separated)
--exclude <patterns> Exclude matching endpoints (comma-separated)
Filtering: Patterns match against category (folder name) or method name. Case-insensitive partial matching.
mcx init
Initialize a new MCX project in the current directory.
mcx init
Creates:
mcx.config.ts- Configuration fileadapters/example.ts- Example adapterskills/hello.ts- Example skill
mcx list
List all available adapters and skills. Alias: mcx ls
mcx list
mcx ls
mcx run
Run a skill or script directly.
# Run a skill by name
mcx run daily-summary date=2024-01-15
# Run a script file
mcx run ./scripts/migrate.ts
MCP Tools
MCX exposes three tools to the AI agent:
| Tool | Description |
|---|---|
mcx_execute |
Execute JavaScript/TypeScript code in sandbox with adapter access |
mcx_run_skill |
Run a named skill with optional inputs |
mcx_list |
List available adapters and skills (read-only) |
Built-in Helpers
Functions available in the sandbox for efficient data handling:
| Helper | Usage | Description |
|---|---|---|
pick(arr, fields) |
pick(data, ['id', 'name']) |
Extract specific fields (supports dot-notation: 'address.city') |
first(arr, n) |
first(data, 5) |
First N items (default: 5) |
sum(arr, field) |
sum(data, 'amount') |
Sum numeric field |
count(arr, field) |
count(data, 'status') |
Count by field value |
table(arr, maxRows) |
table(data, 20) |
Format as markdown table (default: 10 rows) |
Console Methods
All console methods are captured and returned in the response:
console.log('Debug info'); // [LOG] Debug info
console.warn('Warning'); // [WARN] Warning
console.error('Error'); // [ERROR] Error
console.info('Info'); // [INFO] Info
Adapter Access
Adapters are available both via the adapters object and as top-level globals:
// Both work identically
await adapters.crm.getLeads({ limit: 10 });
await crm.getLeads({ limit: 10 });
Usage Patterns
Bad: Raw API response floods context
return await api.getRecords({ limit: 100 });
// 100 objects × 500 tokens each = 50,000 tokens
Good: Filter before returning
const data = await api.getRecords({ limit: 100 });
return pick(data, ['id', 'name', 'status']);
// 100 objects × 3 fields = ~500 tokens
Good: Return summary only
const data = await api.getRecords({ limit: 100 });
return {
count: data.length,
total: sum(data, 'amount'),
byStatus: count(data, 'status')
};
// ~50 tokens
Good: Debug with logs, return minimal
const data = await api.getRecords({ limit: 10 });
console.log(table(pick(data, ['id', 'name', 'amount'])));
return { count: data.length };
// Logs show table, return is tiny
Polling Loop (native control flow)
let found = false;
while (!found) {
const messages = await slack.getChannelHistory({ channel: 'C123' });
found = messages.some(m => m.text.includes('deployment complete'));
if (!found) await new Promise(r => setTimeout(r, 5000));
}
return { status: 'deployment complete' };
// Runs entirely in sandbox, no model round-trips
Generating Adapters
Auto-generate adapters from OpenAPI specs in markdown files.
Interactive TUI
mcx gen
The TUI wizard guides you through:
- Source selection - Single file or batch directory with file browser
- Analysis summary - Shows endpoints, categories, detected auth
- Adapter name - Auto-suggested from API name
- Output path - File browser for destination
- Auth/Base URL - Only asked if not auto-detected
- Config import - Option to add adapter to
mcx.config.ts
Auto-Detection
MCX automatically detects:
- Base URL from OpenAPI
serversfield - Authentication from OpenAPI
securitySchemes:http/basic→ Basic Authhttp/bearer→ Bearer TokenapiKey→ API Key (header or query)
- SDK-based APIs from code examples in markdown (TypeScript and Python)
Batch Processing
Process entire directories of API docs:
mcx gen ./alegra-endpoints -n alegra
# Scans all .md files, extracts OpenAPI specs, generates single adapter
SDK-Based Adapters
When MCX detects SDK usage in docs, it generates SDK wrappers instead of fetch-based code:
// Generated from SDK-based API docs
import { ZepClient } from '@getzep/zep-cloud';
const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY });
export const zep = defineAdapter({
tools: {
addMemory: {
execute: async (params) => client.memory.add(params.sessionId, params),
},
},
});
Creating Adapters
import { defineAdapter } from '@mcx/adapters';
export const myApi = defineAdapter({
name: 'myapi',
description: 'My API adapter',
tools: {
getRecords: {
description: 'Fetch records',
parameters: {
limit: { type: 'number', description: 'Max results' },
},
execute: async (params) => {
return fetch(`${BASE_URL}/records?limit=${params.limit}`).then(r => r.json());
},
},
},
});
Built-in Adapters
Fetch Adapter
Generic HTTP client with all standard methods.
import { createFetchAdapter } from '@mcx/adapters';
const api = createFetchAdapter({
baseUrl: 'https://api.example.com',
headers: { 'X-API-Key': process.env.API_KEY },
timeout: 30000,
});
Tools: get, post, put, patch, delete, head, request
Creating Skills
Skills are reusable operations that combine multiple adapter calls.
Using defineSkill
import { defineSkill } from '@mcx/core';
export const dailySummary = defineSkill({
name: 'daily-summary',
description: 'Summarize daily activity across systems',
adapters: ['crm', 'analytics'],
code: `
const leads = await crm.getLeads({ date: inputs.date });
const visits = await analytics.getPageViews({ date: inputs.date });
return {
date: inputs.date,
leads: leads.length,
visits: sum(visits, 'count'),
conversion: (leads.length / sum(visits, 'count') * 100).toFixed(2) + '%'
};
`,
sandbox: {
timeout: 10000,
memoryLimit: 128,
},
});
Using skillBuilder (Fluent API)
import { skillBuilder } from '@mcx/core';
export const processData = skillBuilder('process-data')
.description('Fetch, transform, and store data')
.requires('api', 'db')
.timeout(15000)
.memoryLimit(256)
.code(`
const raw = await api.fetchRecords({ limit: 1000 });
const filtered = pick(raw, ['id', 'name', 'amount']);
const result = await db.bulkInsert(filtered);
return { inserted: result.count };
`)
.build();
Native Function Skills
For complex logic, use a native TypeScript function instead of code string:
export const complexSkill = defineSkill({
name: 'complex-operation',
description: 'Skill with native TypeScript logic',
adapters: ['api'],
run: async ({ adapters, inputs }) => {
const data = await adapters.api.getData(inputs);
// Complex processing with full TypeScript support
const processed = data
.filter(item => item.active)
.map(item => ({
...item,
score: calculateScore(item),
}))
.sort((a, b) => b.score - a.score);
return { top10: processed.slice(0, 10) };
},
});
Skill Directory Structure
Skills can be single files or directories:
skills/
├── daily-summary.ts # Single file skill
├── complex-workflow/ # Directory skill
│ ├── index.ts # Entry point (required)
│ └── helpers.ts # Supporting modules
Configuration
mcx.config.ts
import { defineConfig } from '@mcx/core';
import { myAdapter } from './adapters/my-adapter';
export default defineConfig({
// Adapters to load
adapters: [myAdapter],
// Skills to load (optional, auto-discovered from skills/)
skills: [],
// Sandbox configuration
sandbox: {
timeout: 5000, // Execution timeout (ms)
memoryLimit: 128, // Memory limit (MB)
allowAsync: true, // Allow async/await
globals: {}, // Custom globals
},
// Environment variables to inject (available in adapters)
env: {
API_KEY: process.env.API_KEY,
},
});
Fluent Config Builder
import { configBuilder } from '@mcx/core';
export default configBuilder()
.adapter(myAdapter)
.adapters(otherAdapter1, otherAdapter2)
.sandbox({ timeout: 10000 })
.build();
Programmatic API
Use MCX programmatically in your own applications:
import { createExecutor } from '@mcx/core';
const executor = createExecutor();
await executor.loadConfig('./mcx.config.ts');
// Execute code
const result = await executor.execute(`
const data = await api.getRecords({ limit: 10 });
return pick(data, ['id', 'name']);
`);
// Run a skill
const skillResult = await executor.runSkill('daily-summary', {
inputs: { date: '2024-01-15' },
});
MCXExecutor Methods
| Method | Description |
|---|---|
loadConfig(path?) |
Load configuration from file |
registerAdapter(adapter) |
Register an adapter |
unregisterAdapter(name) |
Remove an adapter |
getAdapter(name) |
Get adapter by name |
getAdapterNames() |
List all adapter names |
registerSkill(skill) |
Register a skill |
unregisterSkill(name) |
Remove a skill |
getSkill(name) |
Get skill by name |
getSkillNames() |
List all skill names |
execute(code, options?) |
Execute code in sandbox |
runSkill(name, options?) |
Run a skill |
configureSandbox(config) |
Update sandbox defaults |
Claude Code Integration
Add to your project's .mcp.json:
{
"mcpServers": {
"mcx": {
"command": "bun",
"args": ["run", "mcx", "serve"],
"cwd": "/path/to/project"
}
}
}
Or with environment variables:
{
"mcpServers": {
"mcx": {
"command": "bun",
"args": ["run", "mcx", "serve"],
"cwd": "/path/to/project",
"env": {
"API_KEY": "your-api-key"
}
}
}
}
Server Options
# Default: stdio transport (for Claude Code)
mcx serve
# HTTP transport (for testing, other MCP clients, custom integrations)
mcx serve -t http -p 3100
# Specify working directory
mcx serve -c /path/to/project
| Option | Description |
|---|---|
-t, --transport |
stdio (default) or http |
-p, --port |
HTTP port (default: 3100, only for http transport) |
-c, --cwd |
Working directory for config and adapters |
HTTP Transport
When using HTTP transport:
- Server binds to
127.0.0.1only (localhost, for security) - MCP endpoint:
POST /mcp - Health check:
GET /healthreturns{ status, server, version }
Result Summarization
Large results are automatically summarized to prevent context overflow:
- Arrays truncated to 5 items with
"... and N more"indicator - Nested arrays limited to 3 items
- Objects with >5 keys are summarized
Architecture
┌──────────┐ ┌───────────────────────────────────┐
│ Claude │ ───▶ │ MCX Server │
│ /LLM │ code │ ┌─────────┐ ┌─────────────────┐ │
└──────────┘ │ │ Sandbox │ │ Adapters │ │
│ │ (Bun │ │ api.getRecords()│ │
◀───────────│ │ Worker) │ │ api.createItem()│ │
result │ └─────────┘ └─────────────────┘ │
(filtered) │ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Helpers: pick/sum/count/... │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘
Runtime
MCX is 100% Bun-native:
- Sandbox: Bun Workers (native JavaScript isolation)
- HTTP: Bun.serve (no Express)
- Files: Bun.file/Bun.Glob (no node:fs, no glob)
- Env: Automatic .env loading (no dotenv)
Benefits:
- Faster startup (~100ms)
- Smaller bundle (~0.5MB vs 1.5MB)
- No native module compilation issues
- Single runtime (no Node.js required)
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.