
Mcp Dynamic Orchestrator
- 148 installs
- 202 repo stars
- Updated August 4, 2026
- secondsky/claude-skills
Helps with ai & agent building tasks during AI-assisted development.
About
mcp-dynamic-orchestrator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- mcp-dynamic-orchestrator
- AI & Agent Building
- AI-coding skill
Mcp Dynamic Orchestrator by the numbers
- 148 all-time installs (skills.sh)
- +12 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #3,388 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/secondsky/claude-skills --skill mcp-dynamic-orchestratorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 148 |
|---|---|
| repo stars | ★ 202 |
| Last updated | August 4, 2026 |
| Repository | secondsky/claude-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Overview
Use this skill to:
- Discover which MCP servers are available and what they are for.
- Inspect a specific MCP's capabilities without loading all tool schemas.
- Execute TypeScript/JavaScript that calls MCP tools via generated
mcp-clients/*modules.
If no MCP servers are configured, list_mcp_capabilities will respond with an empty list and a message pointing to skills/mcp-dynamic-orchestrator/mcp.registry.json so the user can add MCP entries.
This skill reads from mcp.registry.json, so adding an MCP entry there (for example the Cloudflare MCP) automatically makes it discoverable without changing tool wiring.
Cloudflare MCP example
The Cloudflare MCP server can be configured in mcp.registry.json like this:
{
"id": "cloudflare",
"title": "Cloudflare platform MCP",
"summary": "Interact with Cloudflare's MCP endpoint for documentation, examples, and platform operations exposed via the official Cloudflare MCP server.",
"mcp": {
"transport": "stdio",
"command": "npx",
"args": [
"mcp-remote",
"https://docs.mcp.cloudflare.com/sse"
]
},
"domains": ["cloudflare", "workers", "kv", "r2", "queues", "zero_trust", "networking", "security", "observability"],
"tags": ["cloudflare", "platform", "infra", "docs", "workers", "mcp"],
"examples": [
"Fetch Cloudflare Workers documentation for a specific API.",
"Search Cloudflare platform docs for queues or KV usage patterns.",
"Look up configuration guidance for Zero Trust or networking features."
],
"sensitivity": "low",
"visibility": "default",
"priority": 10,
"autoDiscoverTools": true
}With this entry present:
list_mcp_capabilitieswill returncloudflarewhen queries mention Cloudflare, Workers, KV, R2, Queues, etc.describe_mcpwithid: "cloudflare"will surface concise tool summaries from the Cloudflare MCP server.execute_mcp_codelets the agent write TypeScript such as:
import * as cloudflare from "mcp-clients/cloudflare";
async function main() {
const docs = await cloudflare.search_docs({ query: "Workers KV" });
console.log(docs.summary);
}The actual available functions under mcp-clients/cloudflare are generated dynamically from the MCP tool definitions; the agent should always: 1. Discover via list_mcp_capabilities. 2. Inspect via describe_mcp to see available operations. 3. Use those operations via execute_mcp_code.
How to use
1. Call list_mcp_capabilities with a natural language query or filters to see which MCPs exist. 2. For a chosen MCP (e.g. cloudflare), call describe_mcp to understand its operations. 3. Write TypeScript/JavaScript that imports from mcp-clients/<id> and calls the exported functions. 4. Run your code with execute_mcp_code, optionally restricting allowedMcpIds for safety.
Rules
- Do not assume individual MCP tools are top-level tools.
- Always: discover → describe → generate code →
execute_mcp_code. - Request
detail: "schema"indescribe_mcponly when exact parameter shapes are required.
Known Limitations
Sandbox Security (CRITICAL)
⚠️ The current sandbox implementation is NOT secure for untrusted code.
- Uses
vm.createContext()which is NOT a security boundary - Can be escaped via prototype pollution, require() manipulation, etc.
- Only enable for Claude-generated code (trusted source)
- Requires
MCP_ORCH_ENABLE_SANDBOX=1environment variable - See
references/security-model.mdfor complete security details
Other Limitations
- No TypeScript compilation: User code in
.tsformat will fail - No module resolution: Imports from
mcp-clients/*don't resolve; use$call()API - Static registry: Adding/removing MCPs requires restart
- Limited error handling: Generic errors for MCP connection failures
For detailed troubleshooting, see references/troubleshooting.md.
Production Status
What's Working ✅:
- Discovery via
list_mcp_capabilities(fully functional) - Inspection via
describe_mcp(fully functional) - Registry management (16 MCPs configured)
- MCP clients (stdio + HTTP transports)
- Safety controls (visibility, sensitivity, policies)
What's Limited 🟡:
- Code execution (requires env flag, sandbox not secure)
- Testing (basic smoke tests only)
What's Planned 🔮:
- Secure sandbox with Worker threads (v1.1)
- TypeScript compilation support (v1.1)
- Module resolution (v1.1)
- Dynamic registry updates (v1.2)
For complete roadmap, see plan.md in repository root.
MCP Protocol Documentation
Protocol: Model Context Protocol (MCP) Transport: JSON-RPC 2.0 Last Updated: 2025-11-11 Spec Version: 1.0
---
Overview
The Model Context Protocol (MCP) is a standard for exposing tools, resources, and context to AI models. It uses JSON-RPC 2.0 as its wire protocol.
Key Concepts:
- MCP Server: Process that exposes tools via JSON-RPC
- MCP Client: Process that calls tools via JSON-RPC
- Transport: stdio (stdin/stdout) or HTTP
- Tool: A function that can be called with typed arguments
This document explains how the orchestrator implements the MCP client side.
---
JSON-RPC 2.0 Basics
Request Format
{
"jsonrpc": "2.0",
"id": 123,
"method": "tool_name",
"params": {
"arg1": "value1",
"arg2": 42
}
}Fields:
jsonrpc: Always"2.0"(required)id: Unique request identifier, number or string (required for requests)method: Tool/function name to call (required)params: Arguments object or array (optional)
Response Format (Success)
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"data": "Response data here"
}
}Fields:
jsonrpc: Always"2.0"(required)id: Matches request ID (required)result: Return value (required for success)
Response Format (Error)
{
"jsonrpc": "2.0",
"id": 123,
"error": {
"code": -32601,
"message": "Method not found",
"data": {
"method": "unknown_tool"
}
}
}Fields:
jsonrpc: Always"2.0"(required)id: Matches request ID (required)error: Error object (required for errors)code: Error code number (required)message: Error message string (required)data: Additional error data (optional)
Standard Error Codes
| Code | Meaning | Use Case |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid Request | Missing required fields |
| -32601 | Method not found | Unknown tool name |
| -32602 | Invalid params | Wrong argument types |
| -32603 | Internal error | Server crashed |
| -32000 to -32099 | Server error | Custom MCP errors |
---
MCP Lifecycle
1. Initialization
Client → Server: initialize request
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "1.0",
"capabilities": {
"tools": {}
},
"clientInfo": {
"name": "mcp-dynamic-orchestrator",
"version": "1.0.0"
}
}
}Server → Client: initialize response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "1.0",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "cloudflare-mcp",
"version": "1.0.0"
}
}
}Client → Server: initialized notification
{
"jsonrpc": "2.0",
"method": "initialized"
}(No id field = notification, no response expected)
---
2. Tool Discovery
Client → Server: tools/list request
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}Server → Client: Tool list response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search_cloudflare_documentation",
"description": "Search Cloudflare platform documentation",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
}
},
"required": ["query"]
}
},
{
"name": "migrate_pages_to_workers_guide",
"description": "Get migration guide for Pages to Workers",
"inputSchema": {
"type": "object",
"properties": {}
}
}
]
}
}Schema Fields:
name: Tool identifier (kebab-case recommended)description: Human-readable descriptioninputSchema: JSON Schema for arguments
---
3. Tool Execution
Client → Server: tools/call request
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "search_cloudflare_documentation",
"arguments": {
"query": "Workers KV API"
}
}
}Server → Client: Tool result
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "# Workers KV API\n\nWorkers KV is a global, low-latency key-value store..."
}
],
"isError": false
}
}Response Structure:
content: Array of content blockstype: "text", "image", "resource", etc.text: Text content (for type="text")isError: Boolean indicating if tool execution failed
---
4. Shutdown
Client → Server: shutdown request
{
"jsonrpc": "2.0",
"id": 4,
"method": "shutdown"
}Server → Client: Shutdown acknowledgment
{
"jsonrpc": "2.0",
"id": 4,
"result": null
}Client: Close connection (no more messages)
---
Transport Layers
Stdio Transport
How it works: 1. Client spawns MCP server as child process 2. Client writes JSON-RPC to server's stdin 3. Server writes JSON-RPC to stdout 4. Server writes logs to stderr (not parsed)
Implementation (src/orchestrator.ts:48-120):
const process = spawn(command, args, {
stdio: ['pipe', 'pipe', 'pipe']
});
// Write request to stdin
process.stdin.write(JSON.stringify(request) + '\n');
// Read response from stdout
process.stdout.on('data', (chunk) => {
const lines = chunk.toString().split('\n');
for (const line of lines) {
if (line.trim()) {
const response = JSON.parse(line);
handleResponse(response);
}
}
});Line Buffering:
- Each JSON-RPC message is on a single line
- Newline
\nseparates messages - Empty lines are ignored
stderr Handling:
process.stderr.on('data', (chunk) => {
console.error('[MCP stderr]', chunk.toString());
});---
HTTP Transport
How it works: 1. Client makes POST request to MCP server URL 2. Request body is JSON-RPC 3. Response body is JSON-RPC
Implementation (src/orchestrator.ts:122-150):
const response = await fetch(mcpUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
id: generateId(),
method: toolName,
params: args
})
});
const result = await response.json();Differences from stdio:
- No process spawning
- Stateless (each request is independent)
- Can use HTTP features (auth, caching, load balancing)
- Higher latency (network round-trip)
Authentication:
const response = await fetch(mcpUrl, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MCP_API_KEY}`
},
body: ...
});---
Orchestrator Implementation Details
Request ID Generation
Purpose: Match responses to requests
Implementation:
let requestId = 0;
function generateId(): number {
return ++requestId;
}Properties:
- Sequential numbers (1, 2, 3, ...)
- Thread-safe (single-threaded JavaScript)
- Simple and predictable
Alternative: UUIDs for distributed systems
import { randomUUID } from 'node:crypto';
function generateId(): string {
return randomUUID();
}---
Response Matching
Problem: Async responses may arrive out of order
Solution: Pending requests map
const pendingRequests = new Map<number, {
resolve: (result: any) => void;
reject: (error: Error) => void;
}>();
async function call(method: string, params: any): Promise<any> {
const id = generateId();
return new Promise((resolve, reject) => {
pendingRequests.set(id, { resolve, reject });
// Send request
send({ jsonrpc: '2.0', id, method, params });
});
}
function handleResponse(response: JsonRpcResponse) {
const pending = pendingRequests.get(response.id);
if (!pending) return;
pendingRequests.delete(response.id);
if (response.error) {
pending.reject(new Error(response.error.message));
} else {
pending.resolve(response.result);
}
}---
Timeout Handling
Implementation:
async function callWithTimeout(
method: string,
params: any,
timeout: number
): Promise<any> {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const result = await call(method, params, { signal: controller.signal });
return result;
} finally {
clearTimeout(timeoutId);
}
}Abort Handling:
signal.addEventListener('abort', () => {
// Clean up pending request
const pending = pendingRequests.get(id);
if (pending) {
pending.reject(new Error('Request timeout'));
pendingRequests.delete(id);
}
// For stdio: kill process
if (process && !process.killed) {
process.kill();
}
});---
Error Handling
JSON-RPC Error → JavaScript Error:
function handleResponse(response: JsonRpcResponse) {
if (response.error) {
const error = new Error(response.error.message);
error.code = response.error.code;
error.data = response.error.data;
throw error;
}
return response.result;
}Common Error Patterns:
try {
const result = await mcp.call('search_docs', { query: 'foo' });
} catch (error) {
if (error.code === -32601) {
console.error('Tool not found:', error.message);
} else if (error.code === -32602) {
console.error('Invalid arguments:', error.data);
} else {
console.error('MCP error:', error.message);
}
}---
MCP-Specific Extensions
Tool Schema (JSON Schema)
Purpose: Describe tool arguments for type-checking and validation
Example:
{
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query",
"minLength": 1
},
"limit": {
"type": "number",
"description": "Max results",
"minimum": 1,
"maximum": 100,
"default": 10
}
},
"required": ["query"]
}
}Validation:
import Ajv from 'ajv';
const ajv = new Ajv();
function validateArgs(schema: object, args: any): void {
const validate = ajv.compile(schema);
if (!validate(args)) {
throw new Error(`Invalid arguments: ${JSON.stringify(validate.errors)}`);
}
}---
Content Types
MCP supports multiple content types in responses:
Text Content
{
"content": [
{
"type": "text",
"text": "Plain text response"
}
]
}Image Content
{
"content": [
{
"type": "image",
"data": "base64-encoded-image-data",
"mimeType": "image/png"
}
]
}Resource Content
{
"content": [
{
"type": "resource",
"resource": {
"uri": "https://example.com/doc.pdf",
"mimeType": "application/pdf",
"text": "Document content..."
}
}
]
}Orchestrator Handling:
function extractTextContent(result: any): string {
if (!result.content) return JSON.stringify(result);
const textBlocks = result.content
.filter(block => block.type === 'text')
.map(block => block.text);
return textBlocks.join('\n\n');
}---
Advanced Features
Streaming Responses
Not yet implemented, but planned for MCP spec v2:
// Request with streaming
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "generate_code",
"arguments": { "prompt": "..." },
"stream": true
}
}
// Partial responses
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [{ "type": "text", "text": "const foo = " }],
"partial": true
}
}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [{ "type": "text", "text": "42;" }],
"partial": false
}
}---
Resource Subscriptions
Subscribe to resource changes:
// Subscribe request
{
"jsonrpc": "2.0",
"id": 6,
"method": "resources/subscribe",
"params": {
"uri": "file:///workspace/config.json"
}
}
// Notification when resource changes
{
"jsonrpc": "2.0",
"method": "resources/updated",
"params": {
"uri": "file:///workspace/config.json"
}
}---
Debugging
Logging Requests
function send(request: JsonRpcRequest) {
console.log('[MCP →]', JSON.stringify(request));
// ... send implementation
}
function handleResponse(response: JsonRpcResponse) {
console.log('[MCP ←]', JSON.stringify(response));
// ... response handling
}Output:
[MCP →] {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
[MCP ←] {"jsonrpc":"2.0","id":1,"result":{...}}
[MCP →] {"jsonrpc":"2.0","id":2,"method":"tools/list"}
[MCP ←] {"jsonrpc":"2.0","id":2,"result":{"tools":[...]}}---
Testing with jq
Stdio transport:
# Start MCP server
npx @modelcontextprotocol/server-time | jq .
# Send initialize request
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | \
npx @modelcontextprotocol/server-time | jq .
# List tools
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | \
npx @modelcontextprotocol/server-time | jq .HTTP transport:
# Call HTTP MCP endpoint
curl -X POST https://mcp.example.com/rpc \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq .---
MCP Inspector Tool
Official MCP debugging tool:
# Install
npm install -g @modelcontextprotocol/inspector
# Run
mcp-inspector npx @modelcontextprotocol/server-timeFeatures:
- Interactive tool explorer
- Request/response viewer
- Schema validator
- Error debugger
---
Best Practices
1. Always Set Timeouts
// ❌ Bad - no timeout
await mcp.call('expensive_operation', {});
// ✅ Good - with timeout
await Promise.race([
mcp.call('expensive_operation', {}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 10000)
)
]);2. Validate Arguments
// ❌ Bad - no validation
await mcp.call('search', { query: undefined });
// ✅ Good - validate before calling
if (!query || typeof query !== 'string') {
throw new Error('query must be a non-empty string');
}
await mcp.call('search', { query });3. Handle Errors Gracefully
// ❌ Bad - error crashes process
const result = await mcp.call('search', { query });
// ✅ Good - error handled
try {
const result = await mcp.call('search', { query });
return result;
} catch (error) {
console.error('MCP search failed:', error);
return { error: error.message };
}4. Retry Transient Failures
async function callWithRetry(
mcp: McpClient,
method: string,
params: any,
maxRetries = 2
): Promise<any> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await mcp.call(method, params);
} catch (error) {
if (attempt === maxRetries) throw error;
if (error.code === -32603) {
// Internal error - retry
await sleep(1000 * (attempt + 1)); // Exponential backoff
continue;
}
throw error; // Non-retriable error
}
}
}---
Common Issues
Issue 1: "Parse error" (-32700)
Cause: Invalid JSON sent to MCP server
Solution: Validate JSON before sending
try {
JSON.parse(JSON.stringify(request));
} catch (error) {
console.error('Invalid JSON:', request);
throw error;
}Issue 2: "Method not found" (-32601)
Cause: Tool name misspelled or not available
Solution: List tools first
const tools = await mcp.call('tools/list');
const toolNames = tools.tools.map(t => t.name);
if (!toolNames.includes(toolName)) {
throw new Error(`Tool ${toolName} not found. Available: ${toolNames.join(', ')}`);
}Issue 3: "Invalid params" (-32602)
Cause: Wrong argument types or missing required fields
Solution: Validate against schema
const tools = await mcp.call('tools/list');
const tool = tools.tools.find(t => t.name === toolName);
if (tool.inputSchema) {
validateArgs(tool.inputSchema, args);
}Issue 4: Process Hangs
Cause: No newline after JSON-RPC message (stdio transport)
Solution: Always append \n
// ❌ Bad
process.stdin.write(JSON.stringify(request));
// ✅ Good
process.stdin.write(JSON.stringify(request) + '\n');---
Resources
- Official MCP Spec: https://modelcontextprotocol.io
- JSON-RPC 2.0 Spec: https://www.jsonrpc.org/specification
- MCP SDK (Python): https://github.com/modelcontextprotocol/python-sdk
- MCP SDK (TypeScript): https://github.com/modelcontextprotocol/typescript-sdk
- Example MCPs: https://github.com/modelcontextprotocol/servers
---
Last Updated: 2025-11-11 Protocol Version: 1.0 Maintainer: Claude Skills Maintainers
MCP Registry Schema Documentation
File: mcp.registry.json Location: skills/mcp-dynamic-orchestrator/mcp.registry.json Format: JSON Last Updated: 2025-11-11
---
Overview
The MCP registry is a single source of truth for all available MCP (Model Context Protocol) servers. It defines:
- How to connect to each MCP (transport, command, args)
- What domains/topics each MCP covers
- Safety controls (visibility, sensitivity, priority)
- Discovery metadata (tags, examples)
Design Goal: Make MCP management declarative - add an entry to the registry, and it's immediately discoverable without code changes.
---
Complete Schema
interface McpRegistry {
servers: McpServer[];
}
interface McpServer {
// Identity
id: string; // REQUIRED: Unique kebab-case identifier
title: string; // REQUIRED: Human-readable name
summary: string; // REQUIRED: One-sentence description
// Connection
mcp: McpTransport; // REQUIRED: How to connect
// Discovery
domains: string[]; // REQUIRED: Subject areas (for search scoring)
tags: string[]; // REQUIRED: Keywords (for search scoring)
examples: string[]; // REQUIRED: Use case descriptions
// Safety
sensitivity: Sensitivity; // REQUIRED: "low" | "medium" | "high"
visibility: Visibility; // REQUIRED: "default" | "opt_in" | "experimental"
priority: number; // REQUIRED: 1-10 (10 = highest priority)
autoDiscoverTools: boolean; // REQUIRED: Whether to load tools automatically
}
interface McpTransport {
transport: "stdio" | "http"; // REQUIRED: Connection method
command: string; // REQUIRED (stdio): Executable command
args: string[]; // REQUIRED (stdio): Command arguments
url?: string; // REQUIRED (http): HTTP endpoint
env?: Record<string, string>; // OPTIONAL: Environment variables
alwaysAllow?: string[]; // OPTIONAL: Tools that skip confirmation
}
type Sensitivity = "low" | "medium" | "high";
type Visibility = "default" | "opt_in" | "experimental";---
Field Descriptions
Identity Fields
id (string, required)
Format: Kebab-case, lowercase, alphanumeric + hyphens only
Purpose: Unique identifier used in code (mcp-clients/{id}/)
Examples:
"cloudflare" // ✅ Good
"nuxt-ui" // ✅ Good
"better-auth" // ✅ Good
"CloudFlare" // ❌ Bad - not lowercase
"nuxt_ui" // ❌ Bad - no underscores
"better auth" // ❌ Bad - no spacesRules:
- Must be unique across all MCPs in registry
- Must be valid as a filesystem directory name
- Must not contain
/,\,., or special characters - Recommended: Match the MCP's npm package name (simplified)
---
title (string, required)
Format: Human-readable display name
Purpose: Shown in discovery results and documentation
Examples:
"Cloudflare platform MCP" // ✅ Good - descriptive
"Nuxt UI MCP" // ✅ Good - brand name clear
"Time MCP" // ✅ Good - simple and clear
"MCP" // ❌ Bad - not descriptive
"The Official Cloudflare..." // ❌ Bad - too verboseRules:
- Should include "MCP" suffix for clarity
- Keep under 50 characters
- Use proper capitalization for brand names
---
summary (string, required)
Format: One sentence (no period needed)
Purpose: Quick description for search results and tool selection
Examples:
"Access Cloudflare's MCP endpoint for documentation and platform operations"
// ✅ Good - states what it does
"Interact with the official Nuxt MCP server for modules and documentation"
// ✅ Good - mentions key features
"Use SwiftLens for Swift and Xcode insights via MCP"
// ✅ Good - clear value proposition
"This is an MCP server that lets you do things with Cloudflare."
// ❌ Bad - vague, unhelpful
"Cloudflare"
// ❌ Bad - not a sentenceRules:
- One sentence, concise (<200 characters)
- Start with a verb ("Access", "Interact with", "Use", "Provide")
- Mention the primary capability
- No period at the end
---
Connection Fields (mcp object)
transport ("stdio" | "http", required)
Purpose: Defines how to communicate with the MCP server
stdio: Spawn a child process, communicate via stdin/stdout
{
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-time"]
}http: Make HTTP requests to a remote endpoint
{
"transport": "http",
"url": "https://api.example.com/mcp"
}When to use stdio:
- MCP is an npm package or local executable
- Low latency required (local process)
- Most common for development
When to use http:
- MCP is a remote service
- MCP is behind authentication/API gateway
- Deploying MCP separately from orchestrator
---
command (string, required for stdio)
Purpose: Executable command to spawn
Common values:
"npx" // Run npm package via npx
"uvx" // Run Python package via uvx (UV's npx equivalent)
"node" // Run local Node.js script
"python" // Run local Python script
"/path/to/binary" // Absolute path to executableExamples:
// npm package (recommended)
{
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-time"]
}
// Python package
{
"command": "uvx",
"args": ["mcp-server-time"]
}
// Local script
{
"command": "node",
"args": ["./scripts/my-mcp-server.js"]
}
// Pre-installed binary
{
"command": "/usr/local/bin/my-mcp",
"args": ["--mode", "server"]
}Rules:
- Must be in PATH or absolute path
- Must be executable
- Use
npxfor npm packages (handles installation) - Use
uvxfor Python packages (handles installation)
---
args (string[], required for stdio)
Purpose: Command-line arguments passed to the command
Common patterns:
// Simple package run
["@modelcontextprotocol/server-time"]
// With npx flags
["-y", "@modelcontextprotocol/server-time"]
// -y = automatically confirm installation
// With canary/pre-release versions
["-y", "shadcn@canary", "registry:mcp"]
// With environment-specific args
["mcp-remote", "https://docs.mcp.cloudflare.com/sse"]
// Multiple flags
["--stdio", "--verbose", "--config", "./config.json"]Rules:
- Order matters (flags before positional args)
- Use
-ywithnpxto avoid interactive prompts - Avoid shell-specific syntax (no pipes, redirects, etc.)
---
url (string, required for http)
Purpose: HTTP endpoint for MCP server
Format: Full URL with protocol
Examples:
"https://api.example.com/mcp" // ✅ Good
"https://mcp.cloudflare.com/sse" // ✅ Good
"http://localhost:3000/mcp" // ✅ Good (dev)
"api.example.com/mcp" // ❌ Bad - missing protocol
"https://api.example.com" // ❌ Bad - missing pathRules:
- Must include protocol (
https://orhttp://) - Must be a valid URL
- Should use HTTPS in production
- Can include port numbers for dev
---
env (object, optional)
Purpose: Environment variables passed to the MCP process
Format: Key-value pairs (all values must be strings)
Examples:
{
"env": {
"REGISTRY_URL": "https://animate-ui.com/r/registry.json",
"DEFAULT_MINIMUM_TOKENS": "500",
"DEBUG": "true"
}
}Common use cases:
- API keys (avoid! Use secrets manager instead)
- Configuration URLs
- Feature flags
- Debug settings
Security Warning:
- ⚠️ Do not store sensitive API keys in the registry
- ⚠️ Use environment variables or secrets manager for credentials
- ⚠️ Registry file may be committed to version control
---
alwaysAllow (string[], optional)
Purpose: List of tool names that skip user confirmation
Use case: For read-only, safe operations that don't need approval
Examples:
{
"alwaysAllow": [
"list_nuxt_modules", // Safe: just lists modules
"search_documentation", // Safe: read-only search
"get_current_time" // Safe: no side effects
]
}Rules:
- Only use for read-only operations
- Never use for operations that:
- Modify data
- Make external API calls with side effects
- Execute code
- Access sensitive information
---
Discovery Fields
domains (string[], required)
Purpose: Subject areas this MCP covers (used for search scoring)
Format: Lowercase, single words or hyphenated phrases
Examples:
// Cloudflare MCP
["cloudflare", "workers", "kv", "r2", "queues", "zero_trust", "networking", "security", "observability"]
// Nuxt MCP
["nuxt", "modules", "vue", "framework"]
// Time MCP
["time", "timezone", "datetime"]
// Better Auth MCP
["auth", "better-auth", "security", "authentication"]How it's used: When a user queries list_mcp_capabilities({ query: "Cloudflare Workers" }): 1. Orchestrator scores each MCP by matching query words to domains 2. "Cloudflare" matches domains: ["cloudflare", ...] → high score 3. "Workers" matches domains: [..., "workers", ...] → even higher score 4. MCPs sorted by score, highest first
Rules:
- Include the main technology/service name
- Include specific features/subdomains
- Use common search terms (what users would type)
- Avoid overly generic terms ("api", "tool", "service")
- 3-10 domains recommended
---
tags (string[], required)
Purpose: Keywords for search (finer-grained than domains)
Format: Lowercase, single words or hyphenated phrases
Examples:
// Cloudflare MCP
["cloudflare", "platform", "infra", "docs", "workers", "mcp"]
// shadcn MCP
["shadcn", "ui", "components", "registry", "react"]
// Playwright MCP
["playwright", "testing", "browser", "automation"]Difference from domains:
- Domains: Broad subject areas ("cloudflare", "workers")
- Tags: Specific keywords ("docs", "infra", "testing")
Rules:
- Include technology names
- Include use case terms ("testing", "docs", "ui")
- Include framework names if relevant ("react", "vue")
- 3-8 tags recommended
---
examples (string[], required)
Purpose: Natural language use cases (shown in search results)
Format: Complete sentences describing when to use this MCP
Examples:
// Cloudflare MCP
[
"Fetch Cloudflare Workers documentation for a specific API.",
"Search Cloudflare platform docs for queues or KV usage patterns.",
"Look up configuration guidance for Zero Trust or networking features."
]
// Nuxt MCP
[
"List Nuxt modules and features.",
"Fetch documentation for specific Nuxt modules."
]
// Time MCP
[
"Query current time in specific timezones via MCP."
]How it's used:
- Shown in
list_mcp_capabilitiesresults - Helps users understand what the MCP can do
- Informs search relevance scoring
Rules:
- 1-5 examples (2-3 ideal)
- Start with action verbs ("Fetch", "Search", "Generate")
- Be specific (mention actual features/APIs)
- Complete sentences with periods
---
Safety Fields
sensitivity ("low" | "medium" | "high", required)
Purpose: Controls timeout and rate limits
Security Impact: Higher sensitivity = stricter limits
| Level | Timeout | Max Calls/Min | Use For |
|---|---|---|---|
low | 10s | 50 | Read-only docs, simple utilities |
medium | 7.5s | 20 | Browser automation, complex queries |
high | 5s | 10 | Security-sensitive, expensive operations |
Examples:
// LOW - Documentation MCP (safe, fast, read-only)
{
"id": "cloudflare",
"sensitivity": "low"
}
// MEDIUM - Browser automation (resource-intensive)
{
"id": "playwright",
"sensitivity": "medium"
}
// HIGH - Security operations (careful control needed)
{
"id": "security-scanner",
"sensitivity": "high"
}Guidelines:
- low: Default for most MCPs (docs, search, utilities)
- medium: Browser automation, AI services, complex processing
- high: Operations with security implications or high cost
---
visibility ("default" | "opt_in" | "experimental", required)
Purpose: Controls when MCP appears in discovery
Security Impact: Prevents accidental use of unstable/dangerous MCPs
| Level | Behavior | Use For |
|---|---|---|
default | Always available | Stable, well-tested MCPs |
opt_in | Must explicitly allow | Experimental or high-resource MCPs |
experimental | Hidden unless specifically requested | Unstable, testing-only MCPs |
Examples:
// DEFAULT - Stable production MCP
{
"id": "cloudflare",
"visibility": "default" // Always shown
}
// OPT_IN - Resource-intensive MCP
{
"id": "playwright",
"visibility": "opt_in" // Requires allowedMcpIds: ["playwright"]
}
// EXPERIMENTAL - Testing only
{
"id": "experimental-feature",
"visibility": "experimental" // Never shown unless specifically requested
}How opt_in works:
// Won't appear in general discovery
list_mcp_capabilities({ query: "browser testing" })
// → Returns: [] (playwright hidden)
// Must explicitly allow
execute_mcp_code({
code: "...",
allowedMcpIds: ["playwright"] // ✅ Now playwright can be used
})---
priority (number 1-10, required)
Purpose: Breaks ties when multiple MCPs match a query
Impact: Higher priority appears first in results
Scale:
10: Critical infrastructure (Cloudflare, primary platform MCPs)8-9: Important tools (Nuxt, shadcn, Better Auth)6-7: Useful utilities (Lucide, Context7, Ultracite)4-5: Specialized tools (Sequential Thinking, grep-mcp)1-3: Rarely needed or experimental
Examples:
// Cloudflare = highest priority (platform-critical)
{ "id": "cloudflare", "priority": 10 }
// Nuxt = high priority (framework docs)
{ "id": "nuxt", "priority": 8 }
// Time = medium priority (utility)
{ "id": "time", "priority": 6 }
// grep-mcp = low priority (niche use case)
{ "id": "grep-mcp", "priority": 5 }Guidelines:
- Only one MCP should have priority
10 - Most MCPs should be 6-8
- Reserve 1-3 for experimental/niche tools
---
autoDiscoverTools (boolean, required)
Purpose: Whether to load tool schemas automatically
Impact:
true: Tools loaded when MCP is describedfalse: Tools only loaded on explicit request
Current recommendation: Always use true
{
"autoDiscoverTools": true // Standard for all MCPs
}Future use case: false for MCPs with 100+ tools where lazy loading is critical
---
Complete Example
{
"id": "cloudflare",
"title": "Cloudflare platform MCP",
"summary": "Interact with Cloudflare's MCP endpoint for documentation and platform operations",
"mcp": {
"transport": "stdio",
"command": "npx",
"args": [
"mcp-remote",
"https://docs.mcp.cloudflare.com/sse"
]
},
"domains": [
"cloudflare",
"workers",
"kv",
"r2",
"queues",
"zero_trust",
"networking",
"security",
"observability"
],
"tags": [
"cloudflare",
"platform",
"infra",
"docs",
"workers",
"mcp"
],
"examples": [
"Fetch Cloudflare Workers documentation for a specific API.",
"Search Cloudflare platform docs for queues or KV usage patterns.",
"Look up configuration guidance for Zero Trust or networking features."
],
"sensitivity": "low",
"visibility": "default",
"priority": 10,
"autoDiscoverTools": true
}---
Validation Rules
Required Fields Checklist
- [ ]
idpresent and unique - [ ]
titlepresent and descriptive - [ ]
summarypresent and one sentence - [ ]
mcp.transportis "stdio" or "http" - [ ]
mcp.commandpresent (if stdio) - [ ]
mcp.argspresent (if stdio) - [ ]
mcp.urlpresent (if http) - [ ]
domainsarray with 3+ entries - [ ]
tagsarray with 3+ entries - [ ]
examplesarray with 1+ entry - [ ]
sensitivityis "low", "medium", or "high" - [ ]
visibilityis "default", "opt_in", or "experimental" - [ ]
priorityis number between 1-10 - [ ]
autoDiscoverToolsis boolean
Common Mistakes
❌ Using underscores in ID
{ "id": "my_mcp" } // Bad
{ "id": "my-mcp" } // Good❌ Missing transport fields
{
"mcp": {
"transport": "stdio"
// Missing: command, args
}
}❌ Sensitivity/visibility typos
{ "sensitivity": "Low" } // Bad - case sensitive
{ "sensitivity": "low" } // Good
{ "visibility": "default" } // Good
{ "visibility": "Default" } // Bad - case sensitive❌ Priority out of range
{ "priority": 0 } // Bad - must be 1-10
{ "priority": 11 } // Bad - must be 1-10
{ "priority": 7 } // Good---
Adding a New MCP (Step-by-Step)
1. Find MCP Package
# Example: Adding @modelcontextprotocol/server-time
npm view @modelcontextprotocol/server-time2. Create Entry Template
{
"id": "time",
"title": "Time MCP",
"summary": "Provide time and timezone utilities via MCP",
"mcp": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-time"]
},
"domains": ["time", "timezone", "datetime"],
"tags": ["time", "timezone", "utility"],
"examples": [
"Query current time in specific timezones.",
"Convert between timezones."
],
"sensitivity": "low",
"visibility": "default",
"priority": 6,
"autoDiscoverTools": true
}3. Test Connection
# Test that the MCP starts correctly
npx -y @modelcontextprotocol/server-time
# Should output MCP server initialization messages
# Press Ctrl+C to exit4. Add to Registry
Edit mcp.registry.json:
{
"servers": [
// ... existing MCPs
{
"id": "time",
// ... your new entry
}
]
}5. Verify Discovery
// Ask Claude Code:
"What MCPs are available for time/timezone operations?"
// Should return your new MCP---
Migration Guide
From Old Format
If you have MCPs configured in a different format:
Old format (claude.ai desktop):
{
"mcpServers": {
"cloudflare": {
"command": "npx",
"args": ["mcp-remote", "https://docs.mcp.cloudflare.com/sse"]
}
}
}New format (mcp.registry.json):
{
"servers": [
{
"id": "cloudflare",
"title": "Cloudflare platform MCP",
"summary": "Interact with Cloudflare's MCP endpoint for documentation",
"mcp": {
"transport": "stdio",
"command": "npx",
"args": ["mcp-remote", "https://docs.mcp.cloudflare.com/sse"]
},
"domains": ["cloudflare", "workers"],
"tags": ["cloudflare", "docs"],
"examples": ["Fetch Cloudflare documentation."],
"sensitivity": "low",
"visibility": "default",
"priority": 10,
"autoDiscoverTools": true
}
]
}Key differences: 1. Nested under servers array 2. Requires id, title, summary 3. Requires discovery fields (domains, tags, examples) 4. Requires safety fields (sensitivity, visibility, priority) 5. Connection under mcp object with explicit transport
---
Future Schema Extensions
Planned for v1.1:
version: Semantic version constraintdependencies: Required npm packagescapabilities: Structured capability declarationsrateLimit: Per-MCP custom rate limitingcaching: Tool response caching policies
Planned for v1.2:
authentication: Auth flow configurationretry: Custom retry policiesfallback: Fallback MCP if this one failsalias: Alternative IDs for backwards compatibility
---
Resources
- MCP Protocol: references/mcp-protocol.md
- Security Model: references/security-model.md
- Troubleshooting: references/troubleshooting.md
- Official MCP Docs: https://modelcontextprotocol.io
---
Last Updated: 2025-11-11 Schema Version: 1.0.0 Maintainer: Claude Skills Maintainers
Security Model Documentation
Last Updated: 2025-11-11 Status: Beta - Current implementation has known limitations Next Review: 2025-12-11
---
Executive Summary
⚠️ CRITICAL: The current sandbox implementation is NOT secure for untrusted code.
Safe for:
- ✅ Claude-generated code (trusted source)
- ✅ Internal tools in controlled environments
- ✅ Development/testing scenarios
NOT safe for:
- ❌ User-provided code (untrusted source)
- ❌ Production with external code execution
- ❌ Multi-tenant environments
---
Architecture Overview
┌──────────────────────────────────────────────────────────┐
│ Security Layers │
│ │
│ 1. POLICY LAYER (✅ Implemented) │
│ - Visibility filtering (default/opt_in/experimental) │
│ - Sensitivity-based timeouts (5s/7.5s/10s) │
│ - Rate limiting (10-50 calls/min) │
│ - Allowed MCP ID allowlist │
│ │
│ 2. MCP CLIENT LAYER (✅ Implemented) │
│ - JSON-RPC request validation │
│ - Transport-level timeouts │
│ - Process lifecycle management │
│ - Error isolation │
│ │
│ 3. SANDBOX LAYER (⚠️ NOT SECURE) │
│ - vm.createContext() - CAN BE ESCAPED │
│ - No filesystem restrictions │
│ - No network restrictions │
│ - No memory/CPU limits │
│ │
└──────────────────────────────────────────────────────────┘---
Layer 1: Policy Controls (✅ Secure)
Visibility Filtering
Purpose: Prevent accidental use of experimental/dangerous MCPs
Implementation: src/orchestrator.ts:189-212
function getMcpExecutionPolicy(mcp: McpServer): ExecutionPolicy {
const basePolicy = {
low: { timeout: 10000, maxCalls: 50 },
medium: { timeout: 7500, maxCalls: 20 },
high: { timeout: 5000, maxCalls: 10 }
}[mcp.sensitivity];
return {
...basePolicy,
requiresOptIn: mcp.visibility === "opt_in" || mcp.visibility === "experimental"
};
}Enforcement:
execute_mcp_code({
code: "...",
allowedMcpIds: ["cloudflare"]
})
// ✅ Can call: cloudflare (explicitly allowed)
// ❌ Cannot call: playwright (not in allowlist, visibility=opt_in)Security properties:
- ✅ Prevents accidental calls to opt_in MCPs
- ✅ Enforces explicit allow lists
- ✅ Cannot be bypassed from code
- ✅ Protects against confused deputy attacks
---
Sensitivity-Based Timeouts
Purpose: Limit resource consumption of expensive operations
Timeout Matrix:
| Sensitivity | Timeout | Max Calls/Min | Rationale |
|---|---|---|---|
| Low | 10s | 50 | Read-only docs, fast operations |
| Medium | 7.5s | 20 | Browser automation, AI services |
| High | 5s | 10 | Security-sensitive, expensive ops |
Implementation: src/orchestrator.ts:152-175
async function callMcpTool(mcpId: string, toolName: string, args: any) {
const mcp = registry.find(m => m.id === mcpId);
const policy = getMcpExecutionPolicy(mcp);
// Enforce timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), policy.timeout);
try {
return await client.call(toolName, args, { signal: controller.signal });
} finally {
clearTimeout(timeoutId);
}
}Security properties:
- ✅ Prevents runaway processes
- ✅ Limits resource consumption
- ✅ Enforced at MCP client layer (cannot be bypassed)
- ✅ Per-MCP granularity
---
Allowed MCP ID Allowlist
Purpose: Principle of least privilege - only grant access to needed MCPs
Implementation: execute_mcp_code parameter
execute_mcp_code({
code: `
import * as cloudflare from "mcp-clients/cloudflare";
import * as nuxt from "mcp-clients/nuxt";
import * as playwright from "mcp-clients/playwright";
// ...
`,
allowedMcpIds: ["cloudflare", "nuxt"]
// ✅ cloudflare, nuxt: Can be called
// ❌ playwright: Will be blocked even if imported
})Enforcement: Code generator only creates modules for allowed MCPs
Security properties:
- ✅ Explicit allowlisting (deny by default)
- ✅ Cannot be bypassed from user code
- ✅ Reduces attack surface
- ✅ Makes audit logs clearer
---
Layer 2: MCP Client Security (✅ Secure)
Transport Isolation
Stdio Transport (src/orchestrator.ts:48-120):
class StdioMcpClient {
private process: ChildProcess;
async start() {
// Spawn isolated process
this.process = spawn(command, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, ...mcpEnv }, // Isolated environment
cwd: process.cwd()
});
// Process lifecycle management
this.process.on('exit', () => this.cleanup());
this.process.on('error', (err) => this.handleError(err));
}
}Security properties:
- ✅ Each MCP runs in separate process
- ✅ Process isolation via OS
- ✅ Automatic cleanup on exit/error
- ✅ Limited environment variables
HTTP Transport (src/orchestrator.ts:122-150):
class HttpMcpClient {
async call(toolName: string, args: any, options: { signal?: AbortSignal }) {
const response = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method: toolName, params: args }),
signal: options.signal // Timeout enforcement
});
return response.json();
}
}Security properties:
- ✅ No local process execution (remote only)
- ✅ Standard HTTP timeouts
- ✅ Fetch API security (CORS, CSP)
- ❌ Depends on remote endpoint security
---
JSON-RPC Validation
Implementation: All MCP calls go through validated JSON-RPC layer
{
jsonrpc: "2.0",
id: generateId(), // ✅ Generated, not user-controlled
method: toolName, // ✅ Validated against schema
params: validateArgs(args) // ✅ Type-checked if schema available
}Security properties:
- ✅ Prevents JSON injection
- ✅ Validates method names
- ✅ Type-checks parameters (if schema available)
- ✅ Prevents request smuggling
---
Layer 3: Sandbox (⚠️ NOT SECURE)
Current Implementation
File: src/sandbox.ts:1-42
import vm from 'node:vm';
function runInSandbox(code: string, modules: Record<string, any>) {
const context = vm.createContext({
console: {
log: (...args) => console.log('[sandbox]', ...args)
}
});
// SECURITY ISSUE: vm.createContext is NOT secure
vm.runInContext(code, context);
}Known Vulnerabilities
1. Prototype Pollution
// Malicious code can escape via prototype
const malicious = `
this.constructor.constructor('return process')().exit()
`;
// ❌ Can access Node.js process object and exit2. Constructor Access
// Access to global constructors
const malicious = `
({}).constructor.constructor('return this')().require('fs').writeFileSync('/tmp/pwned', 'gotcha')
`;
// ❌ Can access require() and filesystem3. Promise Manipulation
// Access global scope via Promise
const malicious = `
Promise.resolve().constructor.constructor('return this')()
`;
// ❌ Can access global scope4. No Resource Limits
// Infinite loop
const malicious = `while(true) {}`;
// ❌ Will hang the process (no CPU limit)
// Memory exhaustion
const malicious = `const arr = []; while(true) arr.push(new Array(1000000))`;
// ❌ Will exhaust memory (no RAM limit)---
Current Mitigations
1. Disabled by Default
// Sandbox only runs if explicitly enabled
if (process.env.MCP_ORCH_ENABLE_SANDBOX !== '1') {
throw new Error('Sandbox not enabled. Set MCP_ORCH_ENABLE_SANDBOX=1');
}Rationale: Prevents accidental unsafe use
2. Documentation Warnings
README.md, SKILL.md, and this file all warn:
- ⚠️ Only use for trusted code
- ⚠️ Not safe for user-provided code
- ⚠️ Can be escaped
3. Policy Layer Protection
Even if sandbox is escaped, policy layer still enforces:
- Allowed MCP IDs
- Timeouts
- Rate limits
But: Escaped code can access filesystem, network, etc.
---
Future: Secure Sandbox (v1.1)
Option 1: Worker Threads (Recommended)
Implementation Plan:
import { Worker } from 'node:worker_threads';
function runInWorker(code: string, modules: Record<string, any>) {
return new Promise((resolve, reject) => {
const worker = new Worker('./sandbox-worker.js', {
workerData: { code, modules },
resourceLimits: {
maxOldGenerationSizeMb: 128, // Memory limit
maxYoungGenerationSizeMb: 64,
codeRangeSizeMb: 8
}
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
});
// Timeout enforcement
setTimeout(() => {
worker.terminate();
reject(new Error('Worker timeout'));
}, 10000);
});
}Security properties:
- ✅ True process isolation (separate V8 isolate)
- ✅ Memory limits enforced
- ✅ Can be terminated forcefully
- ✅ No shared memory with main process
- ❌ Still has Node.js APIs (need to restrict)
---
Option 2: Isolated VM (More Secure)
Implementation Plan:
import ivm from 'isolated-vm';
async function runInIsolatedVM(code: string, modules: Record<string, any>) {
const isolate = new ivm.Isolate({ memoryLimit: 128 });
const context = await isolate.createContext();
// Inject only safe modules
for (const [name, module] of Object.entries(modules)) {
await context.global.set(name, new ivm.ExternalCopy(module).copyInto());
}
const script = await isolate.compileScript(code);
return script.run(context, { timeout: 10000 });
}Security properties:
- ✅ True V8 isolate (no access to Node.js)
- ✅ Memory limits enforced
- ✅ CPU timeout enforced
- ✅ No filesystem/network access
- ✅ Safe for untrusted code
- ❌ Requires native module (complex setup)
---
Option 3: WebAssembly Sandbox
Implementation Plan:
// Compile user code to WebAssembly
// Run in WASI sandbox with explicit capabilities
import { WASI } from 'wasi';
const wasi = new WASI({
args: [],
env: {},
preopens: {} // No filesystem access
});
// Load user code as WebAssembly module
const wasm = await WebAssembly.compile(userCodeAsWasm);
const instance = await WebAssembly.instantiate(wasm, {
wasi_snapshot_preview1: wasi.wasiImport
});
wasi.start(instance);Security properties:
- ✅ True sandboxing (WASI capabilities model)
- ✅ No filesystem access unless explicitly granted
- ✅ No network access unless explicitly granted
- ✅ Memory-safe by design
- ❌ Requires compilation to WebAssembly (complex)
- ❌ Limited language support
---
Recommended Implementation (v1.1)
Phase 1: Worker Threads with Restrictions
- Use Worker threads for isolation
- Restrict require() to allowlist
- Provide custom console/fetch implementations
- Enforce resource limits
Phase 2: Isolated VM for Full Security
- Migrate to
isolated-vmpackage - Remove all Node.js APIs
- Provide only MCP client functions
- Full untrusted code support
Timeline:
- Phase 1: 8-12 hours (v1.1, estimated 2025-12-01)
- Phase 2: 16-20 hours (v1.2, estimated 2026-01-01)
---
Security Best Practices
For Developers Using This Skill
1. Only Enable Sandbox for Trusted Code
# Don't set this unless you trust the code source
export MCP_ORCH_ENABLE_SANDBOX=12. Use Minimal Allowed MCP IDs
// ✅ Good - only what's needed
execute_mcp_code({
code: "...",
allowedMcpIds: ["cloudflare"]
});
// ❌ Bad - overly permissive
execute_mcp_code({
code: "...",
allowedMcpIds: ["*"] // Don't do this!
});3. Monitor MCP Call Patterns
- Log all execute_mcp_code calls
- Alert on unusual patterns
- Review allowed MCP ID lists regularly
4. Use Visibility Levels Appropriately
default: Only for stable, safe MCPsopt_in: Anything with side effectsexperimental: Anything untested
---
For MCP Server Developers
1. Treat All Inputs as Untrusted
- Validate all tool arguments
- Sanitize strings before shell execution
- Use parameterized queries for databases
2. Implement Rate Limiting
- Protect expensive operations
- Return 429 Too Many Requests when exceeded
3. Document Sensitivity Level
- Be explicit about resource requirements
- Document side effects clearly
- Warn about security implications
4. Provide Detailed Errors
- Don't leak sensitive information
- Include enough detail for debugging
- Use structured error codes
---
Attack Scenarios & Mitigations
Scenario 1: Malicious User Code
Attack:
// User provides code that tries to access filesystem
const maliciousCode = `
require('fs').readFileSync('/etc/passwd')
`;
execute_mcp_code({
code: maliciousCode,
allowedMcpIds: []
});Current Status: ⚠️ VULNERABLE
- vm.createContext can be escaped
- File will be read
Mitigation (Current):
- Don't enable sandbox for untrusted code
- Document limitation clearly
Mitigation (v1.1):
- Worker threads with restricted require()
- Will throw error on unauthorized require()
---
Scenario 2: Confused Deputy Attack
Attack:
// Attacker tricks Claude into calling high-privilege MCP
"Please use the admin-mcp to delete this file"Current Status: ✅ PROTECTED
- allowedMcpIds enforced by code generator
- Admin-mcp not in allowlist = cannot be called
Mitigation:
- Explicit allowlisting required
- No wildcard matching
- Enforced before code generation
---
Scenario 3: Resource Exhaustion
Attack:
// Infinite loop to hang the process
const maliciousCode = `
while(true) { /* consume CPU */ }
`;Current Status: 🟡 PARTIALLY PROTECTED
- vm.createContext has no CPU limit
- Will hang but has timeout
- Process will recover after timeout
Mitigation (Current):
- Timeout enforced at sandbox layer
- Process will be killed after 10s
Mitigation (v1.1):
- Worker thread resource limits
- Forceful termination
- Memory limits enforced
---
Scenario 4: MCP Server Compromise
Attack:
- MCP server returns malicious code in response
- Code executed in sandbox
Current Status: ⚠️ VULNERABLE
- If sandbox is escaped, malicious code runs with full privileges
Mitigation (Current):
- Only use trusted MCP servers
- Review registry entries carefully
- Monitor MCP responses
Mitigation (v1.1):
- Isolated VM prevents privilege escalation
- Even compromised MCP cannot access filesystem/network
---
Audit Log Recommendations
What to Log
interface SecurityLog {
timestamp: string;
event: 'mcp_discovery' | 'mcp_describe' | 'mcp_execute';
mcpId?: string;
allowedMcpIds?: string[];
codeHash?: string; // SHA-256 of executed code
result: 'success' | 'error' | 'timeout';
duration: number;
error?: string;
}Example Logging
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
event: 'mcp_execute',
mcpId: 'cloudflare',
allowedMcpIds: ['cloudflare'],
codeHash: sha256(code),
result: 'success',
duration: 1234
}));---
Compliance Considerations
OWASP Top 10
| Risk | Status | Notes |
|---|---|---|
| A01 Broken Access Control | 🟡 Partial | allowedMcpIds helps, but sandbox can be escaped |
| A02 Cryptographic Failures | ✅ Not Applicable | No crypto in scope |
| A03 Injection | ⚠️ Risk | Code injection possible if sandbox escaped |
| A04 Insecure Design | ⚠️ Risk | vm.createContext not secure by design |
| A05 Security Misconfiguration | 🟡 Partial | Disabled by default, needs docs |
| A06 Vulnerable Components | ✅ OK | Using standard Node.js modules |
| A07 Authentication Failures | ✅ Not Applicable | No auth in scope |
| A08 Software Data Integrity | ✅ OK | No untrusted data sources |
| A09 Logging Failures | 🟡 Partial | Needs structured logging |
| A10 SSRF | ✅ OK | HTTP client uses standard fetch |
Overall Risk Level: 🟡 Medium (current implementation) Target Risk Level: 🟢 Low (v1.1 with Worker threads)
---
Security Roadmap
v1.0 (Current)
- ✅ Policy layer enforcement
- ✅ MCP client isolation
- ⚠️ Sandbox disabled by default
- ✅ Documentation of limitations
v1.1 (Target: 2025-12-01)
- 🔲 Worker threads sandbox
- 🔲 Restricted require() allowlist
- 🔲 Resource limits (memory, CPU)
- 🔲 Structured security logging
- 🔲 Security audit by external team
v1.2 (Target: 2026-01-01)
- 🔲 Isolated VM (isolated-vm package)
- 🔲 Untrusted code support
- 🔲 Multi-tenant safe
- 🔲 WASM sandbox exploration
---
References
- Node.js VM Security: https://nodejs.org/api/vm.html#vm-executing-javascript
"The vm module is not a security mechanism. Do not use it to run untrusted code."
- Worker Threads: https://nodejs.org/api/worker_threads.html
- Isolated VM: https://github.com/laverdet/isolated-vm
- WASI: https://wasi.dev/
- OWASP Top 10: https://owasp.org/Top10/
---
Questions?
- Issue Tracker: https://github.com/secondsky/claude-skills/issues
- Security Contact: security@example.com
- Documentation: See README.md and SKILL.md
---
Last Updated: 2025-11-11 Next Security Review: 2025-12-11 Responsible: Claude Skills Maintainers
Troubleshooting Guide
Last Updated: 2025-11-11 Skill: mcp-dynamic-orchestrator Maintainer: Claude Skills Maintainers
---
Quick Diagnosis
Run this command first:
# Check if skill is installed
ls -la ~/.claude/skills/mcp-dynamic-orchestrator
# Check registry file
cat ~/.claude/skills/mcp-dynamic-orchestrator/mcp.registry.json | jq .servers[].idExpected output:
cloudflare
nuxt
shadcn
playwright
... (16 total MCPs)---
Common Issues
1. "No MCP servers configured"
Symptoms
list_mcp_capabilities({ query: "Cloudflare" })
→ Result: { servers: [], message: "No MCP servers configured" }Causes
-
mcp.registry.jsonis empty or missing - Registry file has invalid JSON
- Registry file is in wrong location
Solutions
Check if file exists:
ls -la skills/mcp-dynamic-orchestrator/mcp.registry.jsonValidate JSON:
jq . skills/mcp-dynamic-orchestrator/mcp.registry.jsonIf it shows error: Fix JSON syntax.
Check file permissions:
chmod 644 skills/mcp-dynamic-orchestrator/mcp.registry.jsonRestore from backup (if you have one):
cp mcp.registry.json.bak mcp.registry.jsonRe-create minimal registry:
{
"servers": [
{
"id": "time",
"title": "Time MCP",
"summary": "Provide time and timezone utilities",
"mcp": {
"transport": "stdio",
"command": "uvx",
"args": ["mcp-server-time"]
},
"domains": ["time", "timezone"],
"tags": ["time", "timezone"],
"examples": ["Query current time"],
"sensitivity": "low",
"visibility": "default",
"priority": 6,
"autoDiscoverTools": true
}
]
}---
2. "MCP connection timeout"
Symptoms
describe_mcp({ id: "cloudflare" })
→ Error: MCP connection timeout after 10sCauses
- MCP server not installed
- MCP server takes >10s to start
- Network issue (for HTTP transport)
- MCP server crashed on startup
Solutions
Test MCP manually:
# For stdio MCPs
npx mcp-remote https://docs.mcp.cloudflare.com/sse
# Should output JSON-RPC messages
# Press Ctrl+C to exit
# For Python MCPs
uvx mcp-server-timeCheck MCP server logs:
# Redirect stderr to see error messages
npx mcp-remote https://docs.mcp.cloudflare.com/sse 2>&1 | tee mcp.logIncrease timeout (temporary workaround):
Edit src/orchestrator.ts:
// Line ~160
const DEFAULT_TIMEOUT = 10000; // Change to 30000Install MCP server explicitly:
# For npm packages
npm install -g mcp-remote
# For Python packages
pip install mcp-server-time
# or
uv tool install mcp-server-timeCheck network (for HTTP MCPs):
curl -X POST https://docs.mcp.cloudflare.com/sse \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'---
3. "execute_mcp_code not enabled"
Symptoms
execute_mcp_code({ code: "...", allowedMcpIds: [...] })
→ Error: Sandbox not enabled. Set MCP_ORCH_ENABLE_SANDBOX=1Cause
Sandbox is disabled by default for security reasons.
Solution
Enable sandbox (only if you trust the code source):
# Temporary (current terminal session)
export MCP_ORCH_ENABLE_SANDBOX=1
# Permanent (add to ~/.bashrc or ~/.zshrc)
echo 'export MCP_ORCH_ENABLE_SANDBOX=1' >> ~/.bashrc
source ~/.bashrc⚠️ Security Warning:
- Only enable for Claude-generated code
- DO NOT enable for user-provided code
- See security-model.md for details
---
4. "Module import failed"
Symptoms
import * as cloudflare from "mcp-clients/cloudflare";
→ Error: Cannot find module 'mcp-clients/cloudflare'Cause
Module resolution not yet implemented in sandbox.
Solution
Use `$call` API instead:
// ❌ This doesn't work yet
import * as cloudflare from "mcp-clients/cloudflare";
const result = await cloudflare.search_docs({ query: "..." });
// ✅ Use this instead
const cloudflare = await import("mcp-clients/cloudflare");
const result = await cloudflare.$call("search_cloudflare_documentation", {
query: "..."
});Why: Module resolution requires custom require() hook, not yet implemented.
Status: Planned for v1.1 (see plan.md)
---
5. "Tool not found"
Symptoms
await cloudflare.$call("unknown_tool", {})
→ Error: Tool 'unknown_tool' not foundCauses
- Tool name misspelled
- Tool not available in this MCP
- MCP server version mismatch
Solutions
List available tools:
describe_mcp({ id: "cloudflare", detail: "full" })
→ Returns: [
{ tool: "search_cloudflare_documentation", ... },
{ tool: "migrate_pages_to_workers_guide", ... }
]Check tool name spelling:
# Search registry for tool mention
jq '.servers[] | select(.id=="cloudflare") | .examples' \
skills/mcp-dynamic-orchestrator/mcp.registry.jsonTest MCP directly:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | \
npx mcp-remote https://docs.mcp.cloudflare.com/sse | \
jq '.result.tools[].name'Update MCP server:
# Clear npx cache
npx clear-npx-cache
# Re-run MCP
npx mcp-remote https://docs.mcp.cloudflare.com/sse---
6. "Invalid arguments"
Symptoms
await cloudflare.$call("search_cloudflare_documentation", {})
→ Error: Invalid arguments: query is requiredCause
Missing required argument or wrong type.
Solutions
Check tool schema:
describe_mcp({ id: "cloudflare", detail: "schema" })
→ Returns: {
tools: [{
name: "search_cloudflare_documentation",
inputSchema: {
type: "object",
properties: {
query: { type: "string" }
},
required: ["query"] // ← query is required
}
}]
}Provide all required arguments:
// ❌ Missing query
await cloudflare.$call("search_cloudflare_documentation", {})
// ✅ With query
await cloudflare.$call("search_cloudflare_documentation", {
query: "Workers KV"
})Check argument types:
// ❌ Wrong type
await cloudflare.$call("search_cloudflare_documentation", {
query: 123 // Should be string
})
// ✅ Correct type
await cloudflare.$call("search_cloudflare_documentation", {
query: "Workers KV" // String
})---
7. "MCP not discovered"
Symptoms
list_mcp_capabilities({ query: "Cloudflare Workers" })
→ Returns: { servers: [] } // Empty, but Cloudflare MCP existsCauses
- Query doesn't match domains/tags
- MCP visibility is opt_in/experimental
- Priority too low
Solutions
Check MCP configuration:
jq '.servers[] | select(.id=="cloudflare") | {domains, tags, visibility, priority}' \
skills/mcp-dynamic-orchestrator/mcp.registry.jsonAdjust query to match domains:
// ❌ Doesn't match domains
list_mcp_capabilities({ query: "documentation" })
// ✅ Matches domains: ["cloudflare", "workers", ...]
list_mcp_capabilities({ query: "Cloudflare Workers" })Check visibility:
# If visibility is "opt_in", must explicitly allow
jq '.servers[] | select(.id=="playwright") | .visibility' \
mcp.registry.json
→ "opt_in"For opt_in MCPs, use execute_mcp_code with explicit allow:
execute_mcp_code({
code: "...",
allowedMcpIds: ["playwright"] // Explicitly allow opt_in MCP
})Increase priority (edit registry):
{
"id": "cloudflare",
"priority": 10 // Higher = appears first
}---
8. "Rate limit exceeded"
Symptoms
→ Error: Rate limit exceeded: max 50 calls/min for sensitivity=lowCause
Too many MCP calls in short time (based on sensitivity level).
Solutions
Wait and retry:
# Rate limits reset after 1 minute
sleep 60Reduce call frequency:
// ❌ Bad - calls in tight loop
for (const query of queries) {
await mcp.$call("search", { query });
}
// ✅ Good - batch queries
await mcp.$call("batch_search", { queries });Increase sensitivity (if operations are expensive):
Edit registry:
{
"id": "my-mcp",
"sensitivity": "medium" // Reduces limit to 20/min but acceptable for expensive ops
}Implement caching:
const cache = new Map();
async function cachedCall(tool, args) {
const key = JSON.stringify({ tool, args });
if (cache.has(key)) {
return cache.get(key);
}
const result = await mcp.$call(tool, args);
cache.set(key, result);
return result;
}---
9. "TypeError: Cannot read property"
Symptoms
TypeError: Cannot read property '$call' of undefined
→ at execute_mcp_codeCauses
- MCP client not initialized
- MCP ID not in allowedMcpIds
- Code generation failed
Solutions
Check allowed MCP IDs:
execute_mcp_code({
code: `
import * as cloudflare from "mcp-clients/cloudflare";
// ...
`,
allowedMcpIds: ["cloudflare"] // ← Must include "cloudflare"
})Verify MCP exists in registry:
jq '.servers[].id' mcp.registry.json | grep cloudflareCheck for typos:
// ❌ Typo in import
import * as cf from "mcp-clients/cloudflair"; // Wrong
// ✅ Correct
import * as cf from "mcp-clients/cloudflare";---
10. "Process exited with code 1"
Symptoms
Error: MCP server exited with code 1Cause
MCP server crashed on startup.
Solutions
Check MCP server logs:
npx mcp-remote https://docs.mcp.cloudflare.com/sse 2>&1Look for:
- Missing dependencies
- Invalid configuration
- Network errors
- Permission issues
Common fixes:
Missing dependency:
npm install -g <missing-package>Invalid node version:
# Check required version
cat package.json | jq .engines
# Update node
nvm install 20
nvm use 20Permission issue:
sudo chown -R $USER ~/.npmEnvironment variable missing:
{
"mcp": {
"env": {
"REQUIRED_VAR": "value" // Add missing env var
}
}
}---
Debugging Techniques
1. Enable Debug Logging
Edit `src/orchestrator.ts`:
const DEBUG = true; // Enable debug logs
function log(...args: any[]) {
if (DEBUG) console.log('[orchestrator]', ...args);
}Output:
[orchestrator] Loading registry...
[orchestrator] Found 16 MCPs
[orchestrator] Starting MCP client: cloudflare
[orchestrator] Sending request: {"jsonrpc":"2.0","id":1,"method":"initialize"}
[orchestrator] Received response: {"jsonrpc":"2.0","id":1,"result":{...}}---
2. Test MCP in Isolation
Create test script (test-mcp.js):
import { spawn } from 'node:child_process';
const mcp = spawn('npx', ['mcp-remote', 'https://docs.mcp.cloudflare.com/sse']);
mcp.stdout.on('data', (chunk) => {
console.log('STDOUT:', chunk.toString());
});
mcp.stderr.on('data', (chunk) => {
console.error('STDERR:', chunk.toString());
});
// Send initialize
mcp.stdin.write(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {}
}) + '\n');
// Send tools/list
setTimeout(() => {
mcp.stdin.write(JSON.stringify({
jsonrpc: '2.0',
id: 2,
method: 'tools/list'
}) + '\n');
}, 1000);Run:
node test-mcp.js---
3. Validate Registry Schema
Create validator (validate-registry.js):
import Ajv from 'ajv';
import fs from 'node:fs';
const schema = {
type: 'object',
properties: {
servers: {
type: 'array',
items: {
type: 'object',
required: ['id', 'title', 'summary', 'mcp', 'domains', 'tags', 'examples', 'sensitivity', 'visibility', 'priority', 'autoDiscoverTools'],
properties: {
id: { type: 'string', pattern: '^[a-z0-9-]+$' },
title: { type: 'string' },
summary: { type: 'string' },
mcp: { type: 'object' },
domains: { type: 'array', items: { type: 'string' } },
tags: { type: 'array', items: { type: 'string' } },
examples: { type: 'array', items: { type: 'string' } },
sensitivity: { enum: ['low', 'medium', 'high'] },
visibility: { enum: ['default', 'opt_in', 'experimental'] },
priority: { type: 'number', minimum: 1, maximum: 10 },
autoDiscoverTools: { type: 'boolean' }
}
}
}
},
required: ['servers']
};
const ajv = new Ajv();
const validate = ajv.compile(schema);
const registry = JSON.parse(fs.readFileSync('mcp.registry.json', 'utf-8'));
if (validate(registry)) {
console.log('✅ Registry is valid');
} else {
console.error('❌ Registry is invalid:', validate.errors);
}Run:
node validate-registry.js---
4. Monitor MCP Calls
Add call logging:
// In src/orchestrator.ts, add to callMcpTool():
console.log(`[${new Date().toISOString()}] Calling ${mcpId}.${toolName}(${JSON.stringify(args)})`);
const result = await client.call(toolName, args);
console.log(`[${new Date().toISOString()}] Result: ${JSON.stringify(result).slice(0, 100)}...`);Output:
[2025-11-11T10:30:45.123Z] Calling cloudflare.search_cloudflare_documentation({"query":"Workers KV"})
[2025-11-11T10:30:46.456Z] Result: {"content":[{"type":"text","text":"# Workers KV\n\nWorkers KV is a globa...---
Performance Issues
Slow Discovery
Symptoms: list_mcp_capabilities takes >5 seconds
Causes:
- Too many MCPs in registry (>50)
- Complex search scoring
Solutions:
Reduce registry size:
{
"servers": [
// Keep only frequently used MCPs
// Move rarely-used to separate registry file
]
}Implement caching:
// In src/orchestrator.ts
const registryCache = {
data: null,
timestamp: 0,
ttl: 60000 // 1 minute
};
function loadRegistry() {
const now = Date.now();
if (registryCache.data && (now - registryCache.timestamp) < registryCache.ttl) {
return registryCache.data;
}
const data = JSON.parse(fs.readFileSync('mcp.registry.json'));
registryCache.data = data;
registryCache.timestamp = now;
return data;
}---
Slow Tool Calls
Symptoms: MCP tool calls take >10 seconds
Causes:
- MCP server is slow
- Network latency (HTTP transport)
- Large response payloads
Solutions:
Switch to local MCP:
{
"mcp": {
"transport": "stdio", // Faster than HTTP
"command": "npx",
"args": ["local-mcp-server"]
}
}Implement response caching:
const responseCache = new Map();
async function cachedMcpCall(mcpId, tool, args) {
const key = `${mcpId}:${tool}:${JSON.stringify(args)}`;
if (responseCache.has(key)) {
return responseCache.get(key);
}
const result = await mcp.call(tool, args);
responseCache.set(key, result);
// Expire after 5 minutes
setTimeout(() => responseCache.delete(key), 300000);
return result;
}Paginate large results:
// ❌ Request all results
await mcp.$call("search", { query: "...", limit: 1000 }); // Slow
// ✅ Request in pages
await mcp.$call("search", { query: "...", limit: 10, offset: 0 }); // Fast---
Getting Help
1. Search Existing Issues
https://github.com/secondsky/claude-skills/issues?q=mcp-dynamic-orchestrator
2. Create New Issue
Template:
## Issue
[Brief description]
## Steps to Reproduce
1. Install skill: `./scripts/install-skill.sh mcp-dynamic-orchestrator`
2. Run: `list_mcp_capabilities({ query: "..." })`
3. See error: `...`
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happened]
## Environment
- OS: macOS 14.0
- Node: v20.10.0
- Claude Code: v1.0.0
- Skill version: v1.0.0
## Logs
[Paste relevant logs]
## Registry Configuration
[Paste relevant part of mcp.registry.json]3. Join Discord
[Link to Discord server] (if available)
4. Email Support
security@example.com (for security issues only)
---
FAQ
Q: Can I use multiple registries? A: Not yet. Planned for v1.2.
Q: Can I add MCPs at runtime? A: Not yet. Requires restart currently. Planned for v1.2.
Q: Is the sandbox secure? A: No, current implementation (vm.createContext) is NOT secure. See security-model.md.
Q: Can I use TypeScript in execute_mcp_code? A: Not yet. Only JavaScript currently supported. TypeScript compilation planned for v1.1.
Q: How do I update an MCP server? A: Clear npx cache: npx clear-npx-cache, then re-run.
Q: Can I run MCPs in parallel? A: Yes, use Promise.all():
const [result1, result2] = await Promise.all([
mcp1.$call("tool1", {}),
mcp2.$call("tool2", {})
]);Q: How do I see MCP server logs? A: Check stderr: process.stderr in stdio transport.
Q: Can I use local MCP servers? A: Yes, use absolute path:
{
"mcp": {
"command": "/path/to/my-mcp-server"
}
}---
Last Updated: 2025-11-11 Maintainer: Claude Skills Maintainers Feedback: https://github.com/secondsky/claude-skills/issues
import { describeMcp } from "../src/orchestrator";
export default async function tool(input: unknown) {
if (!input || typeof input !== "object") {
throw new Error("Missing describe_mcp params");
}
const { id, includeTools, maxTools } = input as any;
if (!id || typeof id !== "string") {
throw new Error("describe_mcp requires 'id' string");
}
return describeMcp({ id, includeTools, maxTools });
}
import { executeMcpCode } from "../src/orchestrator";
export default async function tool(input: unknown) {
if (!input || typeof input !== "object") {
throw new Error("Missing execute_mcp_code params");
}
const { language, files, entrypoint, allowedMcpIds, maxRuntimeMs, maxLogs } = input as any;
return executeMcpCode({
language,
files,
entrypoint,
allowedMcpIds,
maxRuntimeMs,
maxLogs,
});
}
import { listMcpCapabilities } from "../src/orchestrator";
export default async function tool(input: unknown) {
const params = (input && typeof input === "object") ? (input as any) : {};
return listMcpCapabilities({
query: params.query,
visibility: params.visibility,
});
}