UNPKG

context-optimizer-mcp-server

Version:

Context optimization tools MCP server for AI coding assistants - compatible with GitHub Copilot, Cursor AI, and other MCP-supporting assistants

150 lines (147 loc) 7.34 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RunAndExtractTool = void 0; const execa_1 = __importDefault(require("execa")); const base_1 = require("./base"); const pathValidator_1 = require("../security/pathValidator"); const commandValidator_1 = require("../security/commandValidator"); const factory_1 = require("../providers/factory"); const manager_1 = require("../config/manager"); const manager_2 = require("../session/manager"); class RunAndExtractTool extends base_1.BaseMCPTool { name = 'runAndExtract'; description = 'Execute terminal commands and intelligently extract specific information from their output. Supports cross-platform command execution with security controls.'; inputSchema = { type: 'object', properties: { terminalCommand: { type: 'string', description: 'Shell command to execute. Must be non-interactive (no user input prompts). Navigation commands (cd, pushd, etc.) are not allowed - use workingDirectory instead.' }, extractionPrompt: { type: 'string', description: 'Natural language description of what information to extract from the command output. Examples: "Show me the raw output", "Summarize the results", "Extract all error messages", "Find version numbers", "List all files", "Did the command succeed?", "Are there any warnings?"' }, workingDirectory: { type: 'string', description: 'Full absolute path where command should be executed (e.g., "C:\\Users\\username\\project", "/home/user/project"). Must be within configured security boundaries.' } }, required: ['terminalCommand', 'extractionPrompt', 'workingDirectory'] }; async execute(args) { try { this.logOperation('Terminal execution started', { command: args.terminalCommand, workingDirectory: args.workingDirectory }); // Validate required fields const fieldError = this.validateRequiredFields(args, ['terminalCommand', 'extractionPrompt', 'workingDirectory']); if (fieldError) { return this.createErrorResponse(fieldError); } // Validate working directory const dirValidation = await pathValidator_1.PathValidator.validateWorkingDirectory(args.workingDirectory); if (!dirValidation.valid) { return this.createErrorResponse(dirValidation.error); } // Validate command security const commandValidation = commandValidator_1.CommandValidator.validateCommand(args.terminalCommand); if (!commandValidation.valid) { return this.createErrorResponse(commandValidation.error); } // Log warnings if any if (commandValidation.warnings) { for (const warning of commandValidation.warnings) { console.warn(`⚠️ [${this.name}] Warning: ${warning}`); } } // Execute command const executionResult = await this.executeCommand(args.terminalCommand, dirValidation.resolvedPath); // Process output with LLM const extractedInfo = await this.processOutputWithLLM(args.terminalCommand, executionResult.output, executionResult.exitCode, args.extractionPrompt); // Save session for follow-up questions await manager_2.SessionManager.saveTerminalSession({ command: args.terminalCommand, output: executionResult.output, exitCode: executionResult.exitCode, extractionPrompt: args.extractionPrompt, extractedInfo, timestamp: new Date().toISOString(), workingDirectory: dirValidation.resolvedPath }); this.logOperation('Terminal execution completed successfully'); return this.createSuccessResponse(extractedInfo); } catch (error) { this.logOperation('Terminal execution failed', { error }); return this.createErrorResponse(`Terminal execution failed: ${error instanceof Error ? error.message : String(error)}`); } } async executeCommand(command, workingDirectory) { const config = manager_1.ConfigurationManager.getConfig(); try { const result = await (0, execa_1.default)(command, { shell: true, cwd: workingDirectory, timeout: config.security.commandTimeout, all: true, // Capture both stdout and stderr reject: false // Don't throw on non-zero exit codes }); return { output: result.all || result.stdout || result.stderr || '', exitCode: result.exitCode || 0, success: result.exitCode === 0 }; } catch (error) { if (error.timedOut) { throw new Error(`Command timed out after ${config.security.commandTimeout}ms. This may indicate an interactive command or infinite loop.`); } // Return partial results if available return { output: error.all || error.stdout || error.stderr || `Command failed: ${error.message}`, exitCode: error.exitCode || 1, success: false }; } } async processOutputWithLLM(command, output, exitCode, extractionPrompt) { const config = manager_1.ConfigurationManager.getConfig(); const provider = factory_1.LLMProviderFactory.createProvider(config.llm.provider); const apiKey = this.getApiKey(config.llm.provider, config.llm); const prompt = this.createTerminalExtractionPrompt(output, extractionPrompt, command, exitCode); const response = await provider.processRequest(prompt, config.llm.model, apiKey); if (!response.success) { throw new Error(`LLM processing failed: ${response.error}`); } return response.content; } createTerminalExtractionPrompt(commandOutput, extractionPrompt, terminalCommand, exitCode) { return `You are an expert at summarizing terminal command output and extracting specific information. Command executed: ${terminalCommand} Exit code: ${exitCode} Extraction request: ${extractionPrompt} Instructions: - Focus only on what the user specifically requested - Be concise and well-formatted - Use markdown formatting for better readability - If the command failed (non-zero exit code), mention this clearly - If there's no relevant information, say so clearly Command output: ${commandOutput}`; } getApiKey(provider, llmConfig) { const keyField = `${provider}Key`; const key = llmConfig[keyField]; if (!key) { throw new Error(`API key not configured for provider: ${provider}`); } return key; } } exports.RunAndExtractTool = RunAndExtractTool; //# sourceMappingURL=runAndExtract.js.map