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
JavaScript
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
;