UNPKG

ultimate-mcp-server

Version:

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

388 lines 16.1 kB
/** * Code Context Analysis Tools * Provides intelligent code context extraction and navigation */ import { z } from 'zod'; import { CodeContextManager } from '../code-context/context-manager.js'; import { Logger } from '../utils/logger.js'; import * as path from 'path'; const logger = new Logger('CodeContextTools'); // Global context manager instance let contextManager = null; // Initialize context manager function getContextManager() { if (!contextManager) { contextManager = new CodeContextManager(); } return contextManager; } // Tool definitions export const extractCodeContext = { name: 'extract_code_context', description: 'Extract intelligent code context from files with smart filtering and token management', inputSchema: z.object({ filePaths: z.array(z.string()).describe('File paths to extract context from'), options: z.object({ maxTokens: z.number().optional().default(8000) .describe('Maximum tokens for context window'), includeImports: z.boolean().optional().default(true), includeExports: z.boolean().optional().default(true), includeDocstrings: z.boolean().optional().default(true), includeComments: z.boolean().optional().default(false), contextLines: z.number().optional().default(5) .describe('Lines of context around key elements'), minRelevance: z.number().optional().default(0.3) .describe('Minimum relevance score (0-1)'), strategy: z.enum(['function-focused', 'class-focused', 'import-focused']) .optional() .describe('Context extraction strategy') }).optional() }).strict(), handler: async (args) => { const manager = getContextManager(); const { filePaths, options = {} } = args; // Apply strategy if specified const extractionOptions = { ...options, languages: options.strategy ? [`strategy:${options.strategy}`] : undefined }; // Build context window const window = await manager.buildContextWindow(filePaths, extractionOptions); return { window: { totalContexts: window.contexts.length, totalTokens: window.totalTokens, maxTokens: window.maxTokens, filesIncluded: Array.from(window.files), summary: window.summary }, contexts: window.contexts.map(ctx => ({ id: ctx.id, filePath: ctx.filePath, type: ctx.type, name: ctx.metadata.name, startLine: ctx.startLine, endLine: ctx.endLine, content: ctx.content, relevanceScore: ctx.relevanceScore, metadata: ctx.metadata })) }; } }; export const analyzeFileStructure = { name: 'analyze_file_structure', description: 'Analyze the structure of a code file including imports, exports, classes, and functions', inputSchema: z.object({ filePath: z.string().describe('File path to analyze') }).strict(), handler: async (args) => { const manager = getContextManager(); const fileContext = await manager.getFileContext(args.filePath); if (!fileContext) { throw new Error(`Could not analyze file: ${args.filePath}`); } return { filePath: fileContext.filePath, language: fileContext.language, structure: { imports: fileContext.imports.map(imp => ({ source: imp.source, specifiers: imp.specifiers, line: imp.line, type: imp.type })), exports: fileContext.exports.map(exp => ({ name: exp.name, type: exp.type, line: exp.line })), classes: fileContext.classes.map(cls => ({ name: cls.name, lines: `${cls.startLine}-${cls.endLine}`, methods: cls.methods.map(m => m.name), extends: cls.extends, implements: cls.implements })), functions: fileContext.functions.map(func => ({ name: func.name, lines: `${func.startLine}-${func.endLine}`, parameters: func.parameters.map(p => p.name), async: func.async, generator: func.generator, complexity: func.complexity })), variables: fileContext.variables.filter(v => v.scope === 'module').map(v => ({ name: v.name, line: v.line, constant: v.constant })) }, outline: fileContext.outline, stats: { totalLines: fileContext.outline.totalLines, hasTests: fileContext.outline.hasTests, hasDocumentation: fileContext.outline.hasDocumentation, importCount: fileContext.imports.length, exportCount: fileContext.exports.length, classCount: fileContext.classes.length, functionCount: fileContext.functions.length } }; } }; export const searchCodeContext = { name: 'search_code_context', description: 'Search for code context based on a query, focusing on relevant functions, classes, and symbols', inputSchema: z.object({ query: z.string().describe('Search query'), searchPaths: z.array(z.string()).describe('Paths to search in'), options: z.object({ maxTokens: z.number().optional().default(8000), minRelevance: z.number().optional().default(0.4), includeReferences: z.boolean().optional().default(false) .describe('Include references to the query') }).optional() }).strict(), handler: async (args) => { const manager = getContextManager(); const { query, searchPaths, options = {} } = args; // Build query-focused context window const window = await manager.buildQueryFocusedWindow(query, searchPaths, options); // Find exact definition if possible const definition = await manager.findDefinition(query, searchPaths); // Find references if requested let references = []; if (options.includeReferences) { references = await manager.findReferences(query, searchPaths); } return { query, definition: definition ? { filePath: definition.filePath, type: definition.type, name: definition.metadata.name, startLine: definition.startLine, content: definition.content } : null, contexts: window.contexts.map(ctx => ({ filePath: ctx.filePath, type: ctx.type, name: ctx.metadata.name || 'unnamed', startLine: ctx.startLine, endLine: ctx.endLine, relevanceScore: ctx.relevanceScore, preview: ctx.content.split('\n').slice(0, 3).join('\n') + '...' })), references: references.map(ref => ({ filePath: ref.filePath, line: ref.metadata.targetLine, preview: ref.content })), summary: { totalContexts: window.contexts.length, totalReferences: references.length, filesSearched: window.files.size, tokensUsed: window.totalTokens } }; } }; export const findSymbolDefinition = { name: 'find_symbol_definition', description: 'Find the definition of a function, class, or variable', inputSchema: z.object({ symbol: z.string().describe('Symbol name to find'), searchPaths: z.array(z.string()).describe('Paths to search in') }).strict(), handler: async (args) => { const manager = getContextManager(); const definition = await manager.findDefinition(args.symbol, args.searchPaths); if (!definition) { return { found: false, symbol: args.symbol, message: `Definition for '${args.symbol}' not found in the specified paths` }; } return { found: true, symbol: args.symbol, definition: { filePath: definition.filePath, type: definition.type, name: definition.metadata.name, startLine: definition.startLine, endLine: definition.endLine, signature: definition.metadata.signature, docstring: definition.metadata.docstring, content: definition.content } }; } }; export const findSymbolReferences = { name: 'find_symbol_references', description: 'Find all references to a function, class, or variable', inputSchema: z.object({ symbol: z.string().describe('Symbol name to find references for'), searchPaths: z.array(z.string()).describe('Paths to search in'), includeDefinition: z.boolean().optional().default(true) .describe('Include the definition in results') }).strict(), handler: async (args) => { const manager = getContextManager(); // Find references const references = await manager.findReferences(args.symbol, args.searchPaths); // Optionally include definition let definition = null; if (args.includeDefinition) { definition = await manager.findDefinition(args.symbol, args.searchPaths); } return { symbol: args.symbol, definition: definition ? { filePath: definition.filePath, line: definition.startLine, type: definition.type } : null, references: references.map(ref => ({ filePath: ref.filePath, line: ref.metadata.targetLine || ref.startLine, context: ref.content, type: 'reference' })), summary: { totalReferences: references.length, filesWithReferences: new Set(references.map(r => r.filePath)).size } }; } }; export const buildSmartContext = { name: 'build_smart_context', description: 'Build an intelligent context window for a specific task or query', inputSchema: z.object({ // Support both parameter formats task: z.string().optional().describe('Task description or query'), query: z.string().optional().describe('Task description or query (alternative to task)'), basePath: z.string().optional().describe('Base path to search from'), paths: z.union([ z.array(z.string()), z.string() ]).optional().describe('Paths to search from (alternative to basePath)'), maxTokens: z.number().optional().describe('Maximum tokens (alternative to options.maxTokens)'), options: z.object({ maxTokens: z.number().optional().default(12000), strategy: z.enum(['comprehensive', 'focused', 'minimal']) .optional().default('focused'), fileTypes: z.array(z.string()).optional() .describe('File extensions to include (e.g., [".ts", ".js"])') }).optional() }).refine((data) => (data.task || data.query) && (data.basePath || data.paths), { message: "Either 'task' or 'query' and either 'basePath' or 'paths' must be provided" }), handler: async (args) => { const manager = getContextManager(); // Normalize parameters to support both formats const task = args.task || args.query; let basePath = args.basePath; // Handle paths array if (!basePath && args.paths) { if (Array.isArray(args.paths)) { basePath = args.paths[0]; // Use first path as base } else if (typeof args.paths === 'string') { // Handle string that might be JSON try { const parsed = JSON.parse(args.paths); basePath = Array.isArray(parsed) ? parsed[0] : args.paths; } catch { basePath = args.paths; } } } // Merge maxTokens from both possible locations const options = { ...args.options, maxTokens: args.maxTokens || args.options?.maxTokens || 12000 }; // Determine extraction options based on strategy const extractionOptions = { maxTokens: options.maxTokens, includeImports: options.strategy === 'comprehensive', includeExports: options.strategy === 'comprehensive', includeDocstrings: true, includeComments: options.strategy === 'comprehensive', minRelevance: options.strategy === 'minimal' ? 0.6 : 0.3 }; // Build query-focused window const window = await manager.buildQueryFocusedWindow(task, [basePath], extractionOptions); // Group contexts by type and file const contextsByFile = new Map(); for (const ctx of window.contexts) { const fileName = path.basename(ctx.filePath); if (!contextsByFile.has(fileName)) { contextsByFile.set(fileName, []); } contextsByFile.get(fileName).push({ type: ctx.type, name: ctx.metadata.name || 'unnamed', lines: `${ctx.startLine}-${ctx.endLine}`, relevance: ctx.relevanceScore }); } return { task, strategy: options.strategy, context: { totalTokens: window.totalTokens, maxTokens: window.maxTokens, filesIncluded: Array.from(window.files), contextCount: window.contexts.length }, fileBreakdown: Array.from(contextsByFile.entries()).map(([file, contexts]) => ({ file, contexts, totalContexts: contexts.length })), topContexts: window.contexts.slice(0, 5).map(ctx => ({ file: path.basename(ctx.filePath), type: ctx.type, name: ctx.metadata.name || 'unnamed', relevance: ctx.relevanceScore, preview: ctx.content.split('\n').slice(0, 3).join('\n') + '...' })), recommendation: window.contexts.length === 0 ? 'No relevant context found. Try broadening the search or adjusting the query.' : window.totalTokens > window.maxTokens * 0.9 ? 'Context window is nearly full. Consider using a more focused strategy.' : 'Context window built successfully with room for additional context if needed.' }; } }; export const clearContextCache = { name: 'clear_context_cache', description: 'Clear the code context cache', inputSchema: z.object({}).strict(), handler: async () => { const manager = getContextManager(); manager.clearCache(); return { message: 'Code context cache cleared successfully', stats: manager.getCacheStats() }; } }; // Export all code context tools export const codeContextTools = [ extractCodeContext, analyzeFileStructure, searchCodeContext, findSymbolDefinition, findSymbolReferences, buildSmartContext, clearContextCache ]; //# sourceMappingURL=code-context-tools.js.map