Skip to main content
Glama

claude-code-mcp

#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'; import child_process from 'child_process'; import * as dotenv from 'dotenv'; import winston from 'winston'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import * as fs from 'fs'; import os from 'os'; // Import the os module const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // ロガーのフォーマット設定を共通化 const createLoggerFormat = () => { return winston.format.combine(winston.format.timestamp(), winston.format.printf(({ timestamp, level, message }) => { return `[${timestamp}] [${level}] ${message}`; })); }; // 初期ロガーの設定(.envの読み込み前に最小限のロガーを設定) const initialLogger = winston.createLogger({ level: 'info', format: createLoggerFormat(), transports: [ new winston.transports.Console() ] }); // MCP Hostからの環境変数を優先し、.envファイルはフォールバックとして扱う if (!process.env.CLAUDE_BIN) { const envPaths = [ path.resolve(__dirname, '../.env'), path.resolve(__dirname, '../../.env'), path.resolve(process.cwd(), '.env'), path.resolve(process.cwd(), 'claude-code-server/.env'), path.resolve(os.homedir(), '.claude-code-mcp.env') // ホームディレクトリの .claude-code-mcp.env ]; for (const envPath of envPaths) { if (fs.existsSync(envPath)) { const result = dotenv.config({ path: envPath }); if (result.error) { initialLogger.error(`Failed to load .env file from ${envPath}: ${result.error.message}`); } else { initialLogger.info(`Successfully loaded .env file from ${envPath}`); initialLogger.debug(`Loaded environment variables: LOG_LEVEL=${process.env.LOG_LEVEL}, CLAUDE_BIN=${process.env.CLAUDE_BIN}`); break; } } else { initialLogger.debug(`CLAUDE_BIN fallback search: environment file at ${envPath} does not exist [MCP Host settings: not found]`); } } } // ログレベルの明示的な確認(デバッグ用) console.log(`Environment variable LOG_LEVEL: ${process.env.LOG_LEVEL}`); initialLogger.debug(`Current initial logger level: ${initialLogger.level}`); // ログファイルパスを決定するシンプルな方法 let logFilePath = null; // 1. まずプロジェクトルートに書き込みを試みる try { const projectLogPath = path.resolve(__dirname, '../../claude-code-server.log'); initialLogger.debug(`Attempting to write to project root log: ${projectLogPath}`); fs.writeFileSync(projectLogPath, `# Log file initialization at ${new Date().toISOString()}\n`, { flag: 'a' }); logFilePath = projectLogPath; console.log(`Created log file in project root: ${logFilePath}`); initialLogger.debug(`Successfully created/accessed log file at: ${logFilePath}`); } catch (err) { console.error(`Error writing to project root: ${err instanceof Error ? err.message : String(err)}`); initialLogger.debug(`Failed to write to project root log with error: ${err instanceof Error ? err.stack : String(err)}`); // 2. 次にホームディレクトリに試みる try { const homeDir = process.env.HOME || process.env.USERPROFILE; if (homeDir) { const homeLogPath = path.resolve(homeDir, '.claude-code-server.log'); initialLogger.debug(`Attempting to write to home directory log: ${homeLogPath}`); fs.writeFileSync(homeLogPath, `# Log file initialization at ${new Date().toISOString()}\n`, { flag: 'a' }); console.log(`Created log file in home directory: ${homeLogPath}`); logFilePath = homeLogPath; initialLogger.debug(`Successfully created/accessed log file at: ${logFilePath}`); } } catch (err2) { console.error(`Error writing to home directory: ${err2 instanceof Error ? err2.message : String(err2)}`); initialLogger.debug(`Failed to write to home directory log with error: ${err2 instanceof Error ? err2.stack : String(err2)}`); // 3. 最後に/tmpに試す try { const tmpPath = '/tmp/claude-code-server.log'; initialLogger.debug(`Attempting to write to temp directory log: ${tmpPath}`); fs.writeFileSync(tmpPath, `# Log file initialization at ${new Date().toISOString()}\n`, { flag: 'a' }); logFilePath = tmpPath; console.log(`Created log file in temp directory: ${logFilePath}`); initialLogger.debug(`Successfully created/accessed log file at: ${logFilePath}`); } catch (err3) { console.error('All log file paths failed. Logs will be console-only.'); initialLogger.debug(`Failed to write to temp directory log with error: ${err3 instanceof Error ? err3.stack : String(err3)}`); logFilePath = null; } } } // 環境変数からログレベルを確実に取得 const logLevel = process.env.LOG_LEVEL || 'info'; console.log(`Setting log level to: ${logLevel}`); initialLogger.debug(`Configured log level from environment: ${logLevel}`); // Winstonロガーの設定 const logger = winston.createLogger({ // 環境変数からログレベルを設定 level: logLevel, format: winston.format.combine(winston.format.timestamp(), winston.format.printf(({ timestamp, level, message }) => { return `[${timestamp}] [${level}] ${message}`; })), transports: [ // コンソールトランスポートもログレベル設定を継承 new winston.transports.Console({ level: logLevel }) ] }); logger.debug('Winston logger created with console transport'); // ファイルトランスポートの追加 if (logFilePath) { try { // ファイルトランスポートを作成 const fileTransport = new winston.transports.File({ filename: logFilePath, // 明示的にログレベルを設定 level: logLevel, options: { flags: 'a' } }); // ファイルトランスポート追加 logger.add(fileTransport); console.log(`Added log file: ${logFilePath}`); logger.debug(`File transport added to logger with level: ${logLevel}`); // 同期書き込みテスト - シンプルな起動メッセージのみに置き換え fs.appendFileSync(logFilePath, `# System startup - ${new Date().toISOString()}\n`); logger.debug(`Wrote startup marker to log file`); } catch (err) { console.error('File transport setup error:', err); logger.debug(`Failed to setup file transport: ${err instanceof Error ? err.stack : String(err)}`); } } // 起動時にシンプルなログを書き込み logger.info('=== Claude Code Server started ==='); logger.debug('Server initialization sequence started'); // ファイル情報の診断 - デバッグモードでのみ詳細表示 if (logFilePath && logLevel === 'debug') { try { const stats = fs.statSync(logFilePath); logger.debug(`Log file information (${logFilePath}): size=${stats.size} bytes, mode=${stats.mode.toString(8)}, uid=${stats.uid}, gid=${stats.gid}`); } catch (err) { logger.error('Failed to get file information:', err); } } // ログフラッシュ関数をシンプル化 const flushLog = () => { logger.debug('Flushing logs to disk'); if (logFilePath) { try { // 同期的に書き込み fs.appendFileSync(logFilePath, `\n# Process terminated: ${new Date().toISOString()}\n`); logger.debug('Wrote termination marker to log file'); } catch (appendErr) { console.error('Error writing log on termination:', appendErr); logger.debug(`Failed to write termination marker: ${appendErr instanceof Error ? appendErr.stack : String(appendErr)}`); } } try { // Winstonのクローズを試みる(エラーを無視) logger.debug('Closing Winston logger'); logger.close(); } catch (err) { // 無視 logger.debug(`Error while closing logger: ${err instanceof Error ? err.message : String(err)}`); } }; // プロセス終了時にログを確実にフラッシュ process.on('exit', () => { logger.debug('Process exit event detected'); flushLog(); }); // SIGINT (Ctrl+C) 処理 process.on('SIGINT', () => { logger.info('Received SIGINT. Shutting down.'); logger.debug('SIGINT handler triggered'); flushLog(); process.exit(0); }); // 未処理の例外をキャッチ process.on('uncaughtException', (err) => { logger.error(`Uncaught exception: ${err.message}`); logger.error(err.stack); logger.debug('Uncaught exception handler triggered'); flushLog(); process.exit(1); }); // CLAUDE_BIN Validation let validatedClaudePath = null; const EXPECTED_EXECUTABLE_BASENAME = 'claude'; const IS_WINDOWS = process.platform === 'win32'; const claudeBinFromEnv = process.env.CLAUDE_BIN; if (!claudeBinFromEnv) { logger.error("CRITICAL: CLAUDE_BIN environment variable is not set. This is a required setting. Server exiting."); process.exit(1); } const resolvedPath = path.resolve(claudeBinFromEnv); const basename = path.basename(resolvedPath); let isValidBasename = false; if (IS_WINDOWS) { isValidBasename = (basename.toLowerCase() === EXPECTED_EXECUTABLE_BASENAME || basename.toLowerCase() === `${EXPECTED_EXECUTABLE_BASENAME}.exe`); } else { isValidBasename = (basename === EXPECTED_EXECUTABLE_BASENAME); } if (!isValidBasename) { logger.error(`CRITICAL: CLAUDE_BIN ("${claudeBinFromEnv}") resolved to "${resolvedPath}", which does not have the expected basename "${EXPECTED_EXECUTABLE_BASENAME}". Basename found: "${basename}". Server exiting.`); process.exit(1); } try { fs.accessSync(resolvedPath, fs.constants.X_OK); // Check for existence and execute permission validatedClaudePath = resolvedPath; // Set the validated path logger.info(`CLAUDE_BIN validated. Using executable: ${validatedClaudePath}`); // Now, perform the version check using the validated path try { logger.debug(`Checking Claude CLI version at path: ${validatedClaudePath}`); // Quote the path in case it contains spaces, for reliability with execSync const versionOutput = child_process.execSync(`"${validatedClaudePath}" --version`, { encoding: 'utf8' }); logger.info(`Claude CLI Version: ${versionOutput.trim()}`); } catch (err) { logger.warn(`Could not get Claude CLI version from validated path "${validatedClaudePath}". It might not be a valid Claude executable or is not working correctly. Error: ${err instanceof Error ? err.message : String(err)}`); logger.debug(`Failed to execute Claude CLI for version check: ${err instanceof Error ? err.stack : String(err)}`); // Original code continued even if version check failed, so this does too. // The main validation (name, existence, executability) has already passed. } } catch (err) { logger.error(`CRITICAL: CLAUDE_BIN ("${claudeBinFromEnv}") resolved to "${resolvedPath}", but it is not executable or does not exist. Error: ${err instanceof Error ? err.message : String(err)}. Server exiting.`); process.exit(1); } // Base64 エンコード/デコード ヘルパー関数 function encodeText(text) { return Buffer.from(text, 'utf8').toString('base64'); } function decodeText(encoded) { return Buffer.from(encoded, 'base64').toString('utf8'); } class ClaudeCodeServer { constructor() { logger.debug('Initializing Claude Code Server'); this.server = new Server({ name: 'claude-code-server', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, }); logger.debug('Setting up tool handlers'); this.setupToolHandlers(); this.server.onerror = (error) => { logger.error('[MCP Error]', error); logger.debug(`MCP server error details: ${error instanceof Error ? error.stack : JSON.stringify(error)}`); }; process.on('SIGINT', async () => { logger.debug('SIGINT received in server handler'); await this.server.close(); process.exit(0); }); logger.debug('Claude Code Server initialization completed'); } setupToolHandlers() { // ツールリストの設定 logger.debug('Registering ListTools request handler'); this.server.setRequestHandler(ListToolsRequestSchema, async () => { logger.debug('ListTools handler called'); return { tools: [ { name: 'explain_code', description: 'Provides detailed explanation of the given code.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Target code' }, context: { type: 'string', description: 'Additional context', default: '' } }, required: ['code'] } }, { name: 'review_code', description: 'Reviews the given code.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to review' }, focus_areas: { type: 'string', description: 'Areas to focus on', default: '' } }, required: ['code'] } }, { name: 'fix_code', description: 'Fixes bugs or issues in the given code.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to fix' }, issue_description: { type: 'string', description: 'Description of the issue' } }, required: ['code', 'issue_description'] } }, { name: 'edit_code', description: 'Edits the given code based on instructions.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to edit' }, instructions: { type: 'string', description: 'Editing instructions' } }, required: ['code', 'instructions'] } }, { name: 'test_code', description: 'Generates tests for the given code.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to test' }, test_framework: { type: 'string', description: 'Test framework to use', default: '' } }, required: ['code'] } }, { name: 'simulate_command', description: 'Simulates the execution of a given command.', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'Command to execute' }, input: { type: 'string', description: 'Input data', default: '' } }, required: ['command'] } }, { name: 'your_own_query', description: 'Sends a custom query with context.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Query text' }, context: { type: 'string', description: 'Additional context', default: '' } }, required: ['query'] } } ] }; }); // ツール実行リクエスト処理 logger.debug('Registering CallTool request handler'); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logger.debug(`CallTool handler called for tool: ${name} with args: ${JSON.stringify(args, null, 2)}`); const runClaudeCommand = (claudeArgs, stdinInput) => { return new Promise((resolve, reject) => { // タイムアウト設定 (5分) const timeoutMs = 5 * 60 * 1000; let timeoutId; try { // より詳細なデバッグ情報 logger.debug(`Executing Claude CLI at path: ${validatedClaudePath}`); logger.debug(`Claude CLI arguments: ${JSON.stringify(claudeArgs)}`); if (stdinInput) logger.debug(`Input length: ${stdinInput.length} characters`); // 環境変数をログに出力 logger.debug(`Environment PATH: ${process.env.PATH}`); if (validatedClaudePath === null) { logger.error('validatedClaudePath is null. Claude CLI cannot be executed.'); // エラーをクライアントに返すなど、より丁寧なエラー処理を検討してください。 throw new Error('Validated Claude CLI path is not available. Please check CLAUDE_BIN environment variable or server configuration.'); } const proc = child_process.spawn(validatedClaudePath, claudeArgs, { env: { ...process.env }, stdio: ['pipe', 'pipe', 'pipe'] }); // 標準入力がある場合は書き込みと終了 if (stdinInput) { proc.stdin.write(stdinInput); proc.stdin.end(); logger.debug('Wrote input to Claude CLI stdin'); } let stdout = ''; let stderr = ''; proc.stdout.on('data', (data) => { const chunk = data.toString(); stdout += chunk; logger.debug(`Received stdout chunk: ${chunk.length} bytes`); }); proc.stderr.on('data', (data) => { const chunk = data.toString(); stderr += chunk; logger.error(`Claude stderr: ${chunk}`); logger.debug(`Claude stderr output: ${chunk}`); }); // タイムアウト設定 timeoutId = setTimeout(() => { logger.error(`Command timed out after ${timeoutMs / 1000} seconds`); logger.debug('Killing process due to timeout'); proc.kill(); reject(new Error(`Command timed out after ${timeoutMs / 1000} seconds`)); }, timeoutMs); proc.on('close', (code) => { clearTimeout(timeoutId); logger.debug(`Claude process closed with code: ${code}`); if (code === 0) { logger.debug(`Claude command completed successfully, output length: ${stdout.length} bytes`); resolve(stdout.trim()); } else { logger.error(`Command failed with code ${code}`); logger.debug(`stderr: ${stderr}`); reject(new Error(`Command failed with code ${code}: ${stderr}`)); } }); proc.on('error', (err) => { clearTimeout(timeoutId); logger.error("Process spawn error:", err); logger.debug(`Process error details: ${err.stack}`); reject(err); }); } catch (err) { logger.error("Failed to spawn process:", err); logger.debug(`Spawn failure details: ${err instanceof Error ? err.stack : String(err)}`); reject(err); } }); }; try { // 文字列の最大長さを制限する関数 const truncateIfNeeded = (str, maxLength = 10000) => { if (str.length > maxLength) { logger.warn(`Warning: Input too long, truncating (${str.length} -> ${maxLength})`); return str.substring(0, maxLength) + "... [truncated]"; } return str; }; // エラーを適切に処理するために各ケースを try-catch で囲む switch (name) { case 'explain_code': { const { code, context } = args; try { logger.debug(`Processing explain_code request, code length: ${code.length}`); const encodedCode = encodeText(truncateIfNeeded(code)); logger.debug(`Code encoded to base64, length: ${encodedCode.length}`); // ファイルを使用して大きな入力を渡す場合の代替方法 const prompt = `You are super professional engineer. Please kindly provide a detailed explanation of the following Base64 encoded code:\n\n${encodedCode}\n\nAdditional context (if provided):\n${context || 'No additional context provided.'}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } catch (err) { logger.error("Error in explain_code:", err); logger.debug(`explain_code error details: ${err instanceof Error ? err.stack : String(err)}`); throw err; } } case 'review_code': { const { code, focus_areas } = args; try { logger.debug(`Processing review_code request, code length: ${code.length}`); const encodedCode = encodeText(truncateIfNeeded(code)); logger.debug(`Code encoded to base64, length: ${encodedCode.length}`); const prompt = `You are super professional engineer. Please review the following Base64 encoded code. Consider code readability, efficiency, potential bugs, and security vulnerabilities.\n\nCode:\n${encodedCode}\n\nFocus areas (if provided):\n${focus_areas || 'No specific focus areas provided.'}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } catch (err) { logger.error("Error in review_code:", err); logger.debug(`review_code error details: ${err instanceof Error ? err.stack : String(err)}`); throw err; } } case 'fix_code': { const { code, issue_description } = args; logger.debug(`Processing fix_code request, code length: ${code.length}`); const encodedCode = encodeText(truncateIfNeeded(code)); logger.debug(`Code encoded to base64, length: ${encodedCode.length}`); const prompt = `You are super professional engineer. Please fix the following Base64 encoded code, addressing the issue described below:\n\nCode:\n${encodedCode}\n\nIssue description:\n${issue_description ?? 'No specific issue described.'}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } case 'edit_code': { const { code, instructions } = args; logger.debug(`Processing edit_code request, code length: ${code.length}`); const encodedCode = encodeText(truncateIfNeeded(code)); logger.debug(`Code encoded to base64, length: ${encodedCode.length}`); const prompt = `You are super professional engineer. Please edit the following Base64 encoded code according to the instructions provided:\n\nCode:\n${encodedCode}\n\nInstructions:\n${instructions ?? 'No specific instructions provided.'}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } case 'test_code': { const { code, test_framework } = args; logger.debug(`Processing test_code request, code length: ${code.length}`); const encodedCode = encodeText(truncateIfNeeded(code)); logger.debug(`Code encoded to base64, length: ${encodedCode.length}`); const framework = test_framework || 'default'; const prompt = `You are super professional engineer. Please generate tests for the following Base64 encoded code.\n\nCode:\n${encodedCode}\n\nTest framework (if specified):\n${framework || 'No specific framework provided. Please use a suitable default framework.'}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } case 'simulate_command': { const { command, input } = args; logger.debug(`Processing simulate_command request, command: ${command}`); const prompt = `You are super professional engineer. Simulate the execution of the following command:\n\nCommand: ${command}\n\nInput: ${input || 'No input provided.'}\n\nDescribe the expected behavior and output, without actually executing the command.`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } case 'your_own_query': { const { query, context } = args; logger.debug(`Processing your_own_query request, query length: ${query.length}`); const prompt = `Query: ${query} ${context || ''}`; logger.debug('Calling Claude CLI with prompt'); const output = await runClaudeCommand(['--print'], prompt); logger.debug(`Received response from Claude, length: ${output.length}`); return { content: [{ type: 'text', text: output }] }; } default: throw new McpError(404, "Unknown tool: " + name); } } catch (err) { logger.error("Error executing tool:", err); logger.debug(`Tool execution error details: ${err instanceof Error ? err.stack : String(err)}`); throw new McpError(500, err instanceof Error ? err.message : String(err)); } }); } async run() { logger.debug('Starting Claude Code MCP server'); const transport = new StdioServerTransport(); logger.debug('Created StdioServerTransport'); await this.server.connect(transport); logger.info("Claude Code MCP server running on stdio"); logger.debug('Server connected to transport and ready to process requests'); } } const server = new ClaudeCodeServer(); server.run().catch((err) => { logger.error('Failed to start server:', err); logger.debug(`Server start failure details: ${err instanceof Error ? err.stack : String(err)}`); console.error(err); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KunihiroS/claude-code-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server