UNPKG

ultimate-mcp-server

Version:

The definitive all-in-one Model Context Protocol server for AI-assisted coding across 30+ platforms

282 lines 12.8 kB
import { z } from "zod"; import { FileCollector } from "../utils/file-collector.js"; import { Logger } from "../utils/logger.js"; import { MODELS } from "../config/models.js"; const logger = new Logger("CodebaseTools"); import path from "path"; const analyzeCodebaseSchema = z.object({ path: z.string().describe("Starting directory path for analysis"), pattern: z.string().describe("Regex pattern for file matching (e.g., '.*\\.ts$' for TypeScript files)"), exclude: z.string().optional().describe("Regex pattern for files to exclude"), query: z.string().describe("Analysis question or task"), model: z.string().optional().describe("Model to use for analysis (default: auto-select)"), useThinking: z.boolean().optional().describe("Enable deep thinking mode for complex analysis"), strategy: z.enum([ "direct", "chunked", "summarize-first", ]).optional().default("direct").describe("Analysis strategy"), }); export function createCodebaseTools(orchestrator) { return [ { name: "analyze_codebase", description: "Analyze entire codebases or document collections beyond typical context limits", inputSchema: { type: "object", properties: { path: { type: "string", description: "Starting directory path for analysis" }, pattern: { type: "string", description: "Regex pattern for file matching (e.g., '.*\\.ts$' for TypeScript files)" }, exclude: { type: "string", description: "Regex pattern for files to exclude" }, query: { type: "string", description: "Analysis question or task" }, model: { type: "string", description: "Model to use for analysis (default: auto-select)" }, useThinking: { type: "boolean", description: "Enable deep thinking mode for complex analysis" }, strategy: { type: "string", enum: ["direct", "chunked", "summarize-first"], default: "direct", description: "Analysis strategy" } }, required: ["path", "pattern", "query"] }, handler: async (args) => { const input = analyzeCodebaseSchema.parse(args); try { // Create file collector const collector = new FileCollector(); // Convert string patterns to RegExp const pattern = new RegExp(input.pattern); const exclude = input.exclude ? new RegExp(input.exclude) : undefined; // Resolve path const resolvedPath = path.resolve(input.path); logger.info(`Analyzing codebase at: ${resolvedPath}`); // Collect files const files = await collector.collectFiles(resolvedPath, { pattern, exclude, maxFileSize: 10 * 1024 * 1024, // 10MB per file maxTotalSize: 100 * 1024 * 1024, // 100MB total }); const stats = collector.getStats(); if (files.length === 0) { return { error: "No files found matching the specified pattern", stats, }; } // Prepare context based on strategy let analysisPrompt = ""; switch (input.strategy) { case "direct": // Include all files directly analysisPrompt = prepareDirectAnalysis(files, input.query); break; case "chunked": // Analyze in chunks and combine results return await performChunkedAnalysis(files, input.query, input.model, orchestrator, input.useThinking); case "summarize-first": // First summarize each file, then analyze summaries return await performSummarizeFirstAnalysis(files, input.query, input.model, orchestrator, input.useThinking); default: analysisPrompt = prepareDirectAnalysis(files, input.query); } // Select model const model = input.model || selectModelForCodebase(stats.totalSize); // Perform analysis const result = await orchestrator.callModel(model, analysisPrompt, { temperature: 0.3, useThinking: input.useThinking, }); return { result: result.response, stats, model: result.model, filesAnalyzed: files.length, }; } catch (error) { logger.error("Codebase analysis error:", error); return { error: error instanceof Error ? error.message : "Analysis failed", }; } }, tags: ["analysis", "codebase"], }, { name: "find_in_codebase", description: "Search for specific patterns, functions, or implementations in a codebase", inputSchema: { type: "object", properties: { path: { type: "string", description: "Starting directory path" }, searchPattern: { type: "string", description: "What to search for (regex supported)" }, filePattern: { type: "string", description: "File pattern to search in (e.g., '.*\\.py$')" }, contextLines: { type: "number", default: 3, description: "Number of context lines around matches" } }, required: ["path", "searchPattern"] }, handler: async (args) => { const input = args; try { const collector = new FileCollector(); const pattern = new RegExp(input.filePattern || ".*"); const searchRegex = new RegExp(input.searchPattern, "gim"); const files = await collector.collectFiles(path.resolve(input.path), { pattern, maxFileSize: 5 * 1024 * 1024, // 5MB per file for search }); const matches = []; for (const file of files) { const lines = file.content.split("\n"); const fileMatches = []; lines.forEach((line, index) => { if (searchRegex.test(line)) { const start = Math.max(0, index - input.contextLines); const end = Math.min(lines.length - 1, index + input.contextLines); fileMatches.push({ line: index + 1, match: line.trim(), context: lines.slice(start, end + 1).join("\n"), }); } }); if (fileMatches.length > 0) { matches.push({ file: file.path, matches: fileMatches, }); } } return { totalMatches: matches.reduce((sum, f) => sum + f.matches.length, 0), files: matches, searchPattern: input.searchPattern, }; } catch (error) { logger.error("Codebase search error:", error); return { error: error instanceof Error ? error.message : "Search failed", }; } }, tags: ["search", "codebase"], }, ]; } function prepareDirectAnalysis(files, query) { let prompt = `Analyze the following codebase to answer this question: ${query}\n\n`; prompt += "=== CODEBASE FILES ===\n\n"; for (const file of files) { prompt += `--- File: ${file.path} ---\n`; prompt += file.content; prompt += "\n\n"; } prompt += "=== END OF CODEBASE ===\n\n"; prompt += "Please provide a comprehensive analysis based on the above code."; return prompt; } async function performChunkedAnalysis(files, query, model, orchestrator, useThinking) { const chunkSize = 10; // Files per chunk const chunks = []; for (let i = 0; i < files.length; i += chunkSize) { chunks.push(files.slice(i, i + chunkSize)); } logger.info(`Analyzing ${files.length} files in ${chunks.length} chunks`); // Analyze each chunk const chunkResults = []; for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; const chunkPrompt = prepareDirectAnalysis(chunk, query); const result = await orchestrator.callModel(model || "openai/gpt-4o", chunkPrompt, { temperature: 0.3, useThinking }); chunkResults.push({ chunkIndex: i, files: chunk.map(f => f.path), analysis: result.response, }); } // Synthesize results let synthesisPrompt = `Based on the following chunk analyses of a codebase, provide a comprehensive answer to: ${query}\n\n`; for (const chunk of chunkResults) { synthesisPrompt += `=== Chunk ${chunk.chunkIndex + 1} (Files: ${chunk.files.join(", ")}) ===\n`; synthesisPrompt += chunk.analysis + "\n\n"; } const finalResult = await orchestrator.callModel(model || MODELS.SYNTHESIS_MODEL, synthesisPrompt, { temperature: 0.3, useThinking }); return { result: finalResult.response, strategy: "chunked", chunks: chunks.length, filesAnalyzed: files.length, }; } async function performSummarizeFirstAnalysis(files, query, model, orchestrator, useThinking) { logger.info(`Summarizing ${files.length} files before analysis`); // First, summarize each file const summaries = []; for (const file of files) { const summaryPrompt = `Summarize the key functionality and important aspects of this code file:\n\n${file.content}`; const result = await orchestrator.callModel("openai/gpt-4o-mini", summaryPrompt, { temperature: 0.3 }); summaries.push({ file: file.path, summary: result.response, }); } // Then analyze based on summaries let analysisPrompt = `Based on the following file summaries, answer: ${query}\n\n`; for (const summary of summaries) { analysisPrompt += `=== ${summary.file} ===\n${summary.summary}\n\n`; } const finalResult = await orchestrator.callModel(model || MODELS.SYNTHESIS_MODEL, analysisPrompt, { temperature: 0.3, useThinking }); return { result: finalResult.response, strategy: "summarize-first", filesAnalyzed: files.length, summaries: summaries.length, }; } function selectModelForCodebase(totalSize) { // Select model based on context size if (totalSize > 50 * 1024 * 1024) { // > 50MB return MODELS.GEMINI_PRO; // 2M token context window } else if (totalSize > 10 * 1024 * 1024) { // > 10MB return MODELS.GEMINI_2_FLASH; // 1M token context window } else { return MODELS.GPT_4O_MINI; } } //# sourceMappingURL=codebase-tools.js.map