███╗ ███╗ ██████╗██╗ ██╗
████╗ ████║██╔════╝╚██╗██╔╝
██╔████╔██║██║ ╚███╔╝
██║╚██╔╝██║██║ ██╔██╗
██║ ╚═╝ ██║╚██████╗██╔╝ ██╗
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
MCP server that lets AI agents execute code instead of calling tools directly.
Based on Anthropic's code execution article.
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.
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,000Result: 98% token reduction by filtering data inside the execution environment.
| 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. |
# Install globally with bun (recommended)
bun add -g @papicandela/mcx-cli
# Initialize global directory (~/.mcx/)
mcx initRequires Bun: MCX uses Bun for runtime. Install Bun if you haven't already.
git clone https://github.com/schizoidcock/mcx
cd mcx
bun install
bun run build# 1. Initialize global MCX directory
mcx init
# 2. Generate adapters from API docs
mcx gen ./api-docs.md -n myapi
# 3. Add credentials
# Edit ~/.mcx/.env with your API keys
# 4. Start server (that's it!)
mcx serveMCX uses a global directory (~/.mcx/) for all adapters, skills, and configuration. No per-project setup needed.
~/.mcx/
├── adapters/ # All your adapters
│ ├── stripe.ts
│ └── myapi.ts
├── skills/ # Reusable skills
├── mcx.config.ts # Auto-loads all adapters
├── .env # API credentials
└── package.json # Dependencies
git clone https://github.com/schizoidcock/mcx
cd mcx
bun install
bun run build| File | Description |
|---|---|
adapters/adapter.template.ts |
Adapter template with CRUD examples |
skills/skill.template.ts |
Skill template with 3 patterns |
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> Override config directory (default: ~/.mcx/)Features:
- Uses global
~/.mcx/directory by default - Auto-loads all adapters from
~/.mcx/adapters/ - Loads
.envfrom~/.mcx/.env - HTTP transport binds to
127.0.0.1only (localhost) - HTTP mode exposes
/healthendpoint for monitoring
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: ~/.mcx/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.
Initialize the global MCX directory at ~/.mcx/.
mcx initCreates:
~/.mcx/package.json- MCX dependencies~/.mcx/mcx.config.ts- Auto-loading config (discovers all adapters)~/.mcx/adapters/- Directory for your adapters~/.mcx/skills/hello.ts- Example skill~/.mcx/.env- Template for API credentials
Automatically runs bun install to install dependencies.
Update MCX CLI and global installation. Alias: mcx upgrade
# Check versions without updating
mcx update --check
# Update everything (CLI + global ~/.mcx/)
mcx update
# Update CLI only
mcx update --cli
# Clean and update global installation only
mcx update --globalThe --global flag cleans the ~/.mcx/ installation:
- Removes
node_modules/andbun.lockb - Regenerates
mcx.config.tswith latest template - Updates dependencies to latest versions
- Preserves your
adapters/,skills/, and.env
List all available adapters and skills. Alias: mcx ls
mcx list
mcx lsRun 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.tsMCX 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) |
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) |
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] InfoAdapters 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 });return await api.getRecords({ limit: 100 });
// 100 objects × 500 tokens each = 50,000 tokensconst data = await api.getRecords({ limit: 100 });
return pick(data, ['id', 'name', 'status']);
// 100 objects × 3 fields = ~500 tokensconst data = await api.getRecords({ limit: 100 });
return {
count: data.length,
total: sum(data, 'amount'),
byStatus: count(data, 'status')
};
// ~50 tokensconst data = await api.getRecords({ limit: 10 });
console.log(table(pick(data, ['id', 'name', 'amount'])));
return { count: data.length };
// Logs show table, return is tinylet 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-tripsAuto-generate adapters from OpenAPI specs in markdown files.
mcx genThe 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
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)
Process entire directories of API docs:
mcx gen ./alegra-endpoints -n alegra
# Scans all .md files, extracts OpenAPI specs, generates single adapterWhen 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),
},
},
});import { defineAdapter } from '@papicandela/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());
},
},
},
});Generic HTTP client with all standard methods.
import { createFetchAdapter } from '@papicandela/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
Skills are reusable operations that combine multiple adapter calls.
import { defineSkill } from '@papicandela/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,
},
});import { skillBuilder } from '@papicandela/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();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) };
},
});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
import { defineConfig } from '@papicandela/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,
},
});import { configBuilder } from '@papicandela/mcx-core';
export default configBuilder()
.adapter(myAdapter)
.adapters(otherAdapter1, otherAdapter2)
.sandbox({ timeout: 10000 })
.build();Use MCX programmatically in your own applications:
import { createExecutor } from '@papicandela/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' },
});| 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 |
MCX uses a single global directory for all adapters and configuration:
~/.mcx/
├── mcx.config.ts # Auto-loads all adapters
├── adapters/
│ ├── stripe.ts
│ ├── slack.ts # Add adapters here
│ └── myapi.ts
├── skills/
│ └── daily-summary.ts
└── .env # All API credentials
Benefits:
- One place for all adapters - always available
- One
.envfile for all credentials - No per-project configuration needed
- Claude Code config never changes
For isolated project setups, use the -c flag:
mcx serve -c /path/to/projectWhen to use:
- Team environments with different API access
- Isolating adapters per project for security
- Different sandbox configurations per project
Add to your Claude Code settings (~/.claude.json or project's .mcp.json):
{
"mcpServers": {
"mcx": {
"command": "mcx",
"args": ["serve"]
}
}
}That's it! MCX automatically uses ~/.mcx/ for config and adapters.
To use a specific project directory instead:
{
"mcpServers": {
"mcx": {
"command": "mcx",
"args": ["serve", "-c", "/path/to/project"]
}
}
}# Default: uses ~/.mcx/ with stdio transport
mcx serve
# HTTP transport (for testing, other MCP clients)
mcx serve -t http -p 3100
# Use specific project 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 |
Override config directory (default: ~/.mcx/) |
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 }
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
┌──────────┐ ┌───────────────────────────────────┐
│ Claude │ ───▶ │ MCX Server │
│ /LLM │ code │ ┌─────────┐ ┌─────────────────┐ │
└──────────┘ │ │ Sandbox │ │ Adapters │ │
│ │ (Bun │ │ api.getRecords()│ │
◀───────────│ │ Worker) │ │ api.createItem()│ │
result │ └─────────┘ └─────────────────┘ │
(filtered) │ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Helpers: pick/sum/count/... │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘
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)
MIT