Command Executor MCP Server
- src
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// 許可されたコマンドのプレフィックスリスト(環境変数から取得、デフォルト値を設定)
const DEFAULT_ALLOWED_COMMANDS = [
'git',
'ls',
'mkdir',
'cd',
'npm',
'npx',
'python'
];
const getAllowedCommands = (): string[] => {
const envCommands = process.env.ALLOWED_COMMANDS;
if (!envCommands) {
return DEFAULT_ALLOWED_COMMANDS;
}
return envCommands.split(',').map(cmd => cmd.trim());
};
class CommandExecutorServer {
private server: Server;
private allowedCommands: string[];
constructor() {
this.allowedCommands = getAllowedCommands();
this.server = new Server(
{
name: 'command-executor',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
// エラーハンドリング
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private isCommandAllowed(command: string): boolean {
// コマンドの最初の部分(スペース区切りの最初の単語)を取得
const commandPrefix = command.split(' ')[0];
return this.allowedCommands.some(allowed => commandPrefix === allowed);
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_command',
description: '事前に許可されたコマンドを実行します',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: '実行するコマンド',
},
},
required: ['command'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'execute_command') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
const { command } = request.params.arguments as { command: string };
// コマンドが許可されているか確認
if (!this.isCommandAllowed(command)) {
throw new McpError(
ErrorCode.InvalidParams,
`Command not allowed: ${command}. Allowed commands: ${this.allowedCommands.join(', ')}`
);
}
try {
const { stdout, stderr } = await execAsync(command);
return {
content: [
{
type: 'text',
text: stdout || stderr,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Command execution failed: ${error.message}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Command Executor MCP server running on stdio');
console.error('Allowed commands:', this.allowedCommands.join(', '));
}
}
const server = new CommandExecutorServer();
server.run().catch(console.error);