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