#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError
} from '@modelcontextprotocol/sdk/types.js';
import { spawn, exec } from 'child_process';
import fs from 'fs-extra';
import path from 'path';
class MCPClaudeCodeBridge {
constructor() {
this.server = new Server(
{
name: "claude-code-bridge",
version: "1.0.0"
},
{
capabilities: {
tools: {}
}
}
);
this.workingDir = process.env.WORKING_DIR || '/tmp/claude-projects';
this.setupWorkingDir();
this.setupTools();
}
async setupWorkingDir() {
await fs.ensureDir(this.workingDir);
}
setupTools() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "claude_code_task",
description: "Execute a coding task using Claude Code CLI",
inputSchema: {
type: "object",
properties: {
task: {
type: "string",
description: "The coding task to execute"
},
project_path: {
type: "string",
description: "Path to the project directory",
default: ""
}
},
required: ["task"]
}
},
{
name: "create_project",
description: "Create a new project directory",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Project name"
},
description: {
type: "string",
description: "Project description"
}
},
required: ["name"]
}
},
{
name: "list_files",
description: "List files in a project directory",
inputSchema: {
type: "object",
properties: {
project_path: {
type: "string",
description: "Path to the project directory"
}
},
required: ["project_path"]
}
},
{
name: "read_file",
description: "Read contents of a file",
inputSchema: {
type: "object",
properties: {
file_path: {
type: "string",
description: "Path to the file to read"
}
},
required: ["file_path"]
}
},
{
name: "run_command",
description: "Run a shell command in the project directory",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description: "Command to execute"
},
project_path: {
type: "string",
description: "Directory to run command in"
}
},
required: ["command"]
}
}
]
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'claude_code_task':
return await this.executeClaudeCodeTask(args.task, args.project_path);
case 'create_project':
return await this.createProject(args.name, args.description);
case 'list_files':
return await this.listFiles(args.project_path);
case 'read_file':
return await this.readFile(args.file_path);
case 'run_command':
return await this.runCommand(args.command, args.project_path);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error.message}`
);
}
});
}
async executeClaudeCodeTask(task, projectPath = '') {
return new Promise((resolve, reject) => {
const fullPath = projectPath ? path.join(this.workingDir, projectPath) : this.workingDir;
const claudeCode = spawn('claude-code', [task], {
cwd: fullPath,
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }
});
let stdout = '';
let stderr = '';
claudeCode.stdout.on('data', (data) => {
stdout += data.toString();
});
claudeCode.stderr.on('data', (data) => {
stderr += data.toString();
});
claudeCode.on('close', (code) => {
if (code === 0) {
resolve({
content: [
{
type: "text",
text: `Claude Code task completed successfully:\n\n${stdout}`
}
]
});
} else {
resolve({
content: [
{
type: "text",
text: `Claude Code task failed (exit code ${code}):\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`
}
]
});
}
});
claudeCode.on('error', (error) => {
reject(new Error(`Failed to execute Claude Code: ${error.message}`));
});
});
}
async createProject(name, description = '') {
const projectPath = path.join(this.workingDir, name);
await fs.ensureDir(projectPath);
// Create a basic README
const readmeContent = `# ${name}\n\n${description}\n\nGenerated by Claude Code Bridge\n`;
await fs.writeFile(path.join(projectPath, 'README.md'), readmeContent);
return {
content: [
{
type: "text",
text: `Project '${name}' created at ${projectPath}`
}
]
};
}
async listFiles(projectPath) {
const fullPath = path.join(this.workingDir, projectPath);
try {
const files = await fs.readdir(fullPath, { withFileTypes: true });
const fileList = files.map(file => `${file.isDirectory() ? '📁' : '📄'} ${file.name}`).join('\n');
return {
content: [
{
type: "text",
text: `Files in ${projectPath}:\n\n${fileList}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing files: ${error.message}`
}
]
};
}
}
async readFile(filePath) {
try {
const fullPath = path.join(this.workingDir, filePath);
const content = await fs.readFile(fullPath, 'utf8');
return {
content: [
{
type: "text",
text: `Contents of ${filePath}:\n\n\`\`\`\n${content}\n\`\`\``
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error reading file: ${error.message}`
}
]
};
}
}
async runCommand(command, projectPath = '') {
return new Promise((resolve) => {
const fullPath = projectPath ? path.join(this.workingDir, projectPath) : this.workingDir;
exec(command, { cwd: fullPath }, (error, stdout, stderr) => {
let output = '';
if (stdout) output += `STDOUT:\n${stdout}\n\n`;
if (stderr) output += `STDERR:\n${stderr}\n\n`;
if (error) output += `Error: ${error.message}`;
resolve({
content: [
{
type: "text",
text: output || 'Command executed with no output'
}
]
});
});
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("MCP Claude Code Bridge server started");
}
}
// Start the server
const bridge = new MCPClaudeCodeBridge();
bridge.start().catch(console.error);