Skip to main content
Glama

NervusDB MCP Server

Official
by nervusdb
code.ts40.6 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { ProjectService } from '../services/projectService.js'; import { DefinitionLocator, type DefinitionResult, type SymbolType, type SearchMode, } from '../services/definitionLocator.js'; import { ReferencesFinder, type FindReferencesResult } from '../services/referencesFinder.js'; import { CallHierarchyBuilder, type CallHierarchyResult, type CallHierarchyDirection, } from '../services/callHierarchyBuilder.js'; import { CodeSmellDetector, type CodeSmell, type SmellSeverity, type SmellType, type DetectSmellsResult, } from '../services/codeSmellDetector.js'; import { RefactoringSuggester, type RefactoringPattern, type SuggestRefactoringsResult, } from '../services/refactoringSuggester.js'; import { DocumentationGenerator, type DocAnalysisResult, type GenerateDocsResult, } from '../services/documentationGenerator.js'; import type { QueryService } from '../domain/query/queryService.js'; export interface CodeToolDependencies { readFile: typeof fs.readFile; writeFile: typeof fs.writeFile; mkdir: typeof fs.mkdir; projectService?: ProjectService; definitionLocator?: DefinitionLocator; queryService?: QueryService; callHierarchyBuilder?: CallHierarchyBuilder; codeSmellDetector?: CodeSmellDetector; refactoringSuggester?: RefactoringSuggester; documentationGenerator?: DocumentationGenerator; } const defaultDeps: CodeToolDependencies = { readFile: fs.readFile, writeFile: fs.writeFile, mkdir: fs.mkdir, }; function resolveSafePath(projectPath: string, file: string): string { const root = path.resolve(projectPath); const full = path.resolve(root, file); if (!full.startsWith(root)) { throw new Error('拒绝访问项目目录之外的文件'); } return full; } export function registerCodeTools( server: McpServer, deps: Partial<CodeToolDependencies> = {}, ): void { const resolvedDeps = { ...defaultDeps, ...deps } satisfies CodeToolDependencies; // Auto-instantiate DefinitionLocator if QueryService is provided const definitionLocator = deps.definitionLocator ?? (deps.queryService ? new DefinitionLocator({ queryService: deps.queryService }) : undefined); const referencesFinder = deps.queryService ? new ReferencesFinder({ queryService: deps.queryService }) : undefined; const callHierarchyBuilder = deps.callHierarchyBuilder ?? (deps.queryService ? new CallHierarchyBuilder({ queryService: deps.queryService }) : undefined); const codeSmellDetector = deps.codeSmellDetector ?? (deps.queryService ? new CodeSmellDetector({ queryService: deps.queryService }) : undefined); const refactoringSuggester = deps.refactoringSuggester ?? (deps.queryService ? new RefactoringSuggester({ queryService: deps.queryService }) : undefined); const documentationGenerator = deps.documentationGenerator ?? (deps.queryService ? new DocumentationGenerator({ queryService: deps.queryService }) : undefined); server.registerTool( 'code.readFile', { title: 'Read a file from project', description: 'Read file content by project root and relative path', inputSchema: { projectPath: z.string(), file: z.string() }, outputSchema: { content: z.string() }, }, async ({ projectPath, file }) => { const target = resolveSafePath(projectPath, file); const content = await resolvedDeps.readFile(target, 'utf8'); return { content: [{ type: 'text', text: content }], structuredContent: { content } as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.writeFile', { title: 'Write a file (requires confirm)', description: 'Write content to a project file, confirm flag required.', inputSchema: { projectPath: z.string(), file: z.string(), content: z.string(), confirm: z.boolean(), }, outputSchema: { ok: z.boolean() }, }, async ({ projectPath, file, content, confirm }) => { if (!confirm) { throw new Error('危险操作:需要 confirm=true 才能写入文件'); } const target = resolveSafePath(projectPath, file); await resolvedDeps.mkdir(path.dirname(target), { recursive: true }); await resolvedDeps.writeFile(target, content, 'utf8'); return { content: [{ type: 'text', text: 'ok' }], structuredContent: { ok: true } as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.getDefinition', { title: 'Find symbol definition', description: 'Find the definition location of a symbol (function/class/interface/variable). ' + 'Supports exact/prefix/contains/fuzzy matching with confidence scoring. ' + 'Returns file path, line number, and signature. Target: ≥95% accuracy.', inputSchema: { projectPath: z.string().describe('Project root path'), symbolName: z.string().describe('Symbol name to find'), symbolType: z .enum(['function', 'class', 'interface', 'variable', 'constant', 'type', 'enum', 'any']) .default('any') .optional() .describe('Filter by symbol type'), searchMode: z .enum(['exact', 'prefix', 'contains', 'fuzzy']) .default('exact') .optional() .describe('Search algorithm'), filePathHint: z.string().optional().describe('Prefer definitions in this file'), caseSensitive: z.boolean().default(true).optional().describe('Case-sensitive matching'), maxResults: z .number() .int() .min(1) .max(100) .default(10) .optional() .describe('Maximum results'), minConfidence: z .number() .min(0) .max(1) .default(0.5) .optional() .describe('Minimum confidence (0-1)'), }, outputSchema: {}, }, async ({ projectPath, symbolName, symbolType, searchMode, filePathHint, caseSensitive, maxResults, minConfidence, }) => { if (!definitionLocator) { throw new Error('code.getDefinition requires DefinitionLocator (QueryService)'); } const result = await definitionLocator.findDefinition({ projectPath, symbolName, config: { symbolType: symbolType as SymbolType, searchMode: searchMode as SearchMode, filePathHint, caseSensitive, maxResults, minConfidence, }, }); // Format output const formattedOutput = formatDefinitionResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.findReferences', { title: 'Find all references to a symbol', description: 'Finds all references to a symbol (function, class, etc.) across the project. High recall.', inputSchema: { projectPath: z.string().describe('Project root path'), symbolName: z.string().describe('Symbol name to find references for'), symbolType: z .enum(['function', 'class', 'interface', 'variable', 'any']) .default('any') .optional() .describe('Filter by symbol type'), // config options includeDeclaration: z .boolean() .default(true) .optional() .describe('Include definition location in results'), groupByFile: z.boolean().default(true).optional().describe('Group results by file'), maxReferences: z .number() .int() .min(1) .max(1000) .default(500) .optional() .describe('Maximum total references to return'), }, outputSchema: {}, }, async ({ projectPath, symbolName, symbolType, includeDeclaration, groupByFile, maxReferences, }) => { if (!referencesFinder) { throw new Error('code.findReferences requires ReferencesFinder (QueryService)'); } const result = await referencesFinder.findReferences({ projectPath, symbolName, symbolType, config: { includeDeclaration, groupByFile, maxReferences, }, }); const formattedOutput = formatReferencesResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.getCallHierarchy', { title: 'Get call hierarchy visualization', description: 'Visualize function call hierarchy (callers and callees) as an ASCII tree or Mermaid diagram. ' + 'Shows who calls this function (upstream) and what this function calls (downstream).', inputSchema: { projectPath: z.string().describe('Project root path'), symbolName: z.string().describe('Function name to analyze'), symbolType: z .enum(['function', 'method', 'any']) .default('function') .optional() .describe('Filter by symbol type'), // Hierarchy config maxDepth: z .number() .int() .min(1) .max(10) .default(5) .optional() .describe('Maximum tree depth to traverse'), direction: z .enum(['callers', 'callees', 'both']) .default('both') .optional() .describe('Direction: callers (upstream), callees (downstream), or both'), pruneThreshold: z .number() .int() .min(5) .max(100) .default(20) .optional() .describe('Maximum children per node before pruning'), // Output config includeMetadata: z .boolean() .default(true) .optional() .describe('Include file paths and line numbers'), renderMermaid: z .boolean() .default(false) .optional() .describe('Generate Mermaid diagram (can be large)'), }, outputSchema: {}, }, async ({ projectPath, symbolName, symbolType, maxDepth, direction, pruneThreshold, includeMetadata, renderMermaid, }) => { if (!callHierarchyBuilder) { throw new Error('code.getCallHierarchy requires CallHierarchyBuilder (QueryService)'); } const result = await callHierarchyBuilder.buildHierarchy({ projectPath, symbolName, symbolType, config: { maxDepth, direction: direction as CallHierarchyDirection, pruneThreshold, includeMetadata, renderAsciiTree: true, renderMermaidDiagram: renderMermaid, }, }); const formattedOutput = formatCallHierarchyResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.detectSmells', { title: 'Detect code smells', description: 'Detect common code smells and anti-patterns in functions. ' + 'Provides severity levels, explanations, and refactoring suggestions.', inputSchema: { projectPath: z.string().describe('Project root path'), symbols: z.array(z.string()).describe('Function names to analyze'), severityThreshold: z .enum(['INFO', 'WARNING', 'ERROR']) .default('INFO') .optional() .describe('Minimum severity to report'), smellTypes: z .array( z.enum([ 'god-function', 'deep-nesting', 'long-parameters', 'long-function', 'dead-code', 'magic-number', 'duplicated-code', ]), ) .optional() .describe('Specific smell types to check'), // Config maxComplexity: z .number() .int() .min(1) .max(50) .default(10) .optional() .describe('Max function complexity (callee count)'), maxParameters: z .number() .int() .min(1) .max(20) .default(5) .optional() .describe('Max parameter count'), maxFunctionLines: z .number() .int() .min(10) .max(500) .default(50) .optional() .describe('Max function length'), }, outputSchema: {}, }, async ({ projectPath, symbols, severityThreshold, smellTypes, maxComplexity, maxParameters, maxFunctionLines, }) => { if (!codeSmellDetector) { throw new Error('code.detectSmells requires CodeSmellDetector (QueryService)'); } const result = await codeSmellDetector.detectSmells({ projectPath, symbols, severityThreshold: severityThreshold as SmellSeverity, smellTypes: smellTypes as SmellType[], config: { maxComplexity, maxParameters, maxFunctionLines, }, }); const formattedOutput = formatSmellsResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.suggestRefactorings', { title: 'Suggest refactorings for code smells', description: 'Generate intelligent refactoring suggestions based on detected code smells. ' + 'Provides priority scoring, impact analysis, and step-by-step instructions.', inputSchema: { projectPath: z.string().describe('Project root path'), smells: z.array(z.any()).describe('Detected code smells (from detectSmells)'), maxSuggestions: z .number() .int() .min(1) .max(20) .default(10) .optional() .describe('Maximum suggestions to return'), }, outputSchema: {}, }, async ({ projectPath, smells, maxSuggestions }) => { if (!refactoringSuggester) { throw new Error('code.suggestRefactorings requires RefactoringSuggester (QueryService)'); } const result = await refactoringSuggester.suggestRefactorings({ projectPath, smells: smells as CodeSmell[], maxSuggestions, }); const formattedOutput = formatRefactoringSuggestionsResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.analyzeDocumentation', { title: 'Analyze documentation completeness', description: 'Analyze the completeness of code documentation for specified symbols. ' + 'Identifies missing, partial, or complete documentation with specific suggestions.', inputSchema: { projectPath: z.string().describe('Project root path'), symbols: z.array(z.string()).describe('Symbol names to analyze'), }, outputSchema: {}, }, async ({ projectPath, symbols }) => { if (!documentationGenerator) { throw new Error('code.analyzeDocumentation requires DocumentationGenerator (QueryService)'); } const result = await documentationGenerator.analyzeDocumentation({ projectPath, symbols, }); const formattedOutput = formatDocAnalysisResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); server.registerTool( 'code.generateDocumentation', { title: 'Generate documentation for code symbols', description: 'Automatically generate documentation (JSDoc/TSDoc/docstring) for specified symbols. ' + 'Uses context from code structure and naming patterns.', inputSchema: { projectPath: z.string().describe('Project root path'), symbols: z.array(z.string()).describe('Symbol names to document'), format: z .enum(['jsdoc', 'tsdoc', 'python-docstring', 'markdown']) .default('jsdoc') .optional() .describe('Documentation format'), includeExamples: z.boolean().default(true).optional().describe('Include usage examples'), }, outputSchema: {}, }, async ({ projectPath, symbols, format, includeExamples }) => { if (!documentationGenerator) { throw new Error( 'code.generateDocumentation requires DocumentationGenerator (QueryService)', ); } const result = await documentationGenerator.generateDocumentation({ projectPath, symbols, format, includeExamples, }); const formattedOutput = formatGenerateDocsResult(result); return { content: [{ type: 'text', text: formattedOutput }], structuredContent: result as unknown as { [x: string]: unknown }, }; }, ); } /** * Format documentation analysis result */ function formatDocAnalysisResult(result: DocAnalysisResult): string { const lines: string[] = []; // Header lines.push('# Documentation Analysis'); lines.push(''); lines.push(`**Project**: ${result.projectPath}`); lines.push(`**Symbols Analyzed**: ${result.summary.total}`); lines.push(''); // Summary lines.push('## 📊 Summary'); lines.push(''); lines.push( `- ✅ **Complete**: ${result.summary.complete} (${Math.round((result.summary.complete / result.summary.total) * 100)}%)`, ); lines.push( `- ⚠️ **Partial**: ${result.summary.partial} (${Math.round((result.summary.partial / result.summary.total) * 100)}%)`, ); lines.push( `- ❌ **Missing**: ${result.summary.missing} (${Math.round((result.summary.missing / result.summary.total) * 100)}%)`, ); lines.push(''); lines.push(`**Overall Completeness**: ${result.summary.completeness.toFixed(1)}%`); lines.push(''); // Details by status if (result.analysis.length > 0) { lines.push('---'); lines.push('## 📝 Detailed Analysis'); lines.push(''); // Group by status const byStatus = { missing: result.analysis.filter((a) => a.status === 'missing'), partial: result.analysis.filter((a) => a.status === 'partial'), complete: result.analysis.filter((a) => a.status === 'complete'), }; for (const [status, items] of Object.entries(byStatus)) { if (items.length === 0) continue; const icon = status === 'missing' ? '❌' : status === 'partial' ? '⚠️' : '✅'; const label = status.toUpperCase(); lines.push(`### ${icon} ${label} (${items.length})`); lines.push(''); for (const item of items) { lines.push(`#### \`${item.symbolName}\``); lines.push(''); lines.push(`**File**: \`${item.filePath}\``); lines.push(''); if (item.issues.length > 0) { lines.push('**Issues**:'); for (const issue of item.issues) { lines.push(`- ${issue}`); } lines.push(''); } if (item.suggestions.length > 0) { lines.push('**Suggestions**:'); for (const suggestion of item.suggestions) { lines.push(`- ${suggestion}`); } lines.push(''); } lines.push('---'); lines.push(''); } } } // Statistics lines.push('## ⏱️ Statistics'); lines.push(''); lines.push(`- Analysis Time: ${result.stats.analysisTimeMs}ms`); lines.push(''); return lines.join('\n'); } /** * Format documentation generation result */ function formatGenerateDocsResult(result: GenerateDocsResult): string { const lines: string[] = []; // Header lines.push('# Generated Documentation'); lines.push(''); lines.push(`**Project**: ${result.projectPath}`); lines.push(`**Symbols**: ${result.summary.total}`); lines.push(''); // Summary lines.push('## 📊 Summary'); lines.push(''); lines.push(`- ✅ **Successful**: ${result.summary.successful}`); lines.push(`- ❌ **Failed**: ${result.summary.failed}`); lines.push(`- 🎯 **Avg Confidence**: ${(result.summary.avgConfidence * 100).toFixed(1)}%`); lines.push(''); if (result.generated.length === 0) { lines.push('---'); lines.push('No documentation generated.'); return lines.join('\n'); } // Generated docs lines.push('---'); lines.push('## 📝 Generated Documentation'); lines.push(''); for (const doc of result.generated) { lines.push(`### \`${doc.symbolName}\``); lines.push(''); lines.push(`**File**: \`${doc.filePath}\``); lines.push(`**Format**: ${doc.format}`); lines.push(`**Status**: ${doc.status}`); lines.push(`**Confidence**: ${(doc.confidence * 100).toFixed(1)}%`); lines.push(''); if (doc.reasoning) { lines.push(`**Reasoning**: ${doc.reasoning}`); lines.push(''); } lines.push('**Generated Documentation**:'); lines.push(''); lines.push('```'); lines.push(doc.generatedDoc); lines.push('```'); lines.push(''); if (doc.existingDoc) { lines.push('**Existing Documentation**:'); lines.push(''); lines.push('```'); lines.push(doc.existingDoc); lines.push('```'); lines.push(''); } lines.push('---'); lines.push(''); } // Statistics lines.push('## ⏱️ Statistics'); lines.push(''); lines.push(`- Generation Time: ${result.stats.generationTimeMs}ms`); lines.push(''); return lines.join('\n'); } /** * Format definition search result for human-readable output */ function formatDefinitionResult(result: { query: { symbolName: string; searchMode: SearchMode; symbolType?: SymbolType }; definitions: DefinitionResult[]; totalFound: number; searchTimeMs: number; }): string { const lines: string[] = []; // Header lines.push(`# Definition Search: "${result.query.symbolName}"`); lines.push(''); lines.push(`**Search Mode**: ${result.query.searchMode}`); if (result.query.symbolType) { lines.push(`**Symbol Type**: ${result.query.symbolType}`); } lines.push(`**Results**: ${result.definitions.length} of ${result.totalFound} found`); lines.push(`**Search Time**: ${result.searchTimeMs}ms`); lines.push(''); if (result.definitions.length === 0) { lines.push('---'); lines.push(''); lines.push('❌ **No definitions found**'); lines.push(''); lines.push('**Suggestions**:'); lines.push('- Try `searchMode: "contains"` or `"fuzzy"` for partial matching'); lines.push('- Check if the symbol name is correct'); lines.push('- Try with `caseSensitive: false`'); lines.push('- Remove `symbolType` filter to widen search'); return lines.join('\n'); } lines.push('---'); lines.push(''); // Definitions list for (let i = 0; i < result.definitions.length; i++) { const def = result.definitions[i]; const rank = i + 1; // Confidence bar const confidencePct = Math.round(def.confidence * 100); const barLength = Math.min(Math.floor(confidencePct / 5), 20); const bar = '█'.repeat(barLength) + '░'.repeat(20 - barLength); // Confidence color indicator let indicator = '🟢'; if (def.confidence < 0.7) indicator = '🟡'; if (def.confidence < 0.5) indicator = '🟠'; lines.push(`## ${rank}. ${indicator} \`${def.name}\` (${def.type})`); lines.push(''); lines.push(`**Confidence**: ${confidencePct}%`); lines.push(''); lines.push(`\`${bar}\` ${confidencePct}%`); lines.push(''); // Location lines.push('**Location**:'); lines.push(`- File: \`${def.filePath}\``); if (def.startLine !== undefined) { lines.push(`- Line: ${def.startLine}${def.endLine ? `-${def.endLine}` : ''}`); } if (def.language) { lines.push(`- Language: ${def.language}`); } lines.push(''); // Signature if (def.signature) { lines.push('**Signature**:'); lines.push('```' + (def.language || '')); lines.push(def.signature); lines.push('```'); lines.push(''); } // Match reason lines.push('**Why Matched**:'); lines.push(`- ${def.matchReason}`); lines.push(''); if (i < result.definitions.length - 1) { lines.push('---'); lines.push(''); } } // JSON output lines.push(''); lines.push('---'); lines.push(''); lines.push('<details>'); lines.push('<summary>Full JSON Output (click to expand)</summary>'); lines.push(''); lines.push('```json'); lines.push(JSON.stringify(result, null, 2)); lines.push('```'); lines.push('</details>'); return lines.join('\n'); } /** * Format references search result for human-readable output */ function formatReferencesResult(result: FindReferencesResult): string { const lines: string[] = []; // Header lines.push(`# References Search: "${result.query.symbolName}"`); lines.push(''); lines.push(`**Total References Found**: ${result.totalReferences}`); lines.push(`**Search Time**: ${result.searchTimeMs}ms`); lines.push(''); if (result.totalReferences === 0) { lines.push('---'); lines.push('❌ **No references found**'); return lines.join('\n'); } // Definition if (result.definition) { lines.push('---'); lines.push('## 📌 Definition'); lines.push(''); lines.push(`- **Symbol**: \`${result.definition.name}\` (${result.definition.type})`); lines.push(`- **File**: \`${result.definition.filePath}\``); lines.push(`- **Line**: ${result.definition.startLine}`); if (result.definition.signature) { lines.push('- **Signature**:'); lines.push('```' + (result.definition.language || '')); lines.push(result.definition.signature); lines.push('```'); } lines.push(''); } // References lines.push('---'); lines.push('## 🔗 References'); lines.push(''); if (result.fileReferences) { for (const fileGroup of result.fileReferences) { lines.push(`### 📄 \`${fileGroup.file}\` (${fileGroup.referenceCount} references)`); lines.push(''); for (const ref of fileGroup.references) { lines.push(`- **L${ref.location.line}**: [${ref.type}] \`${ref.referrer.name}\``); if (ref.location.context) { lines.push(' ```' + (ref.referrer.language || '')); lines.push(` ${ref.location.context.trim()}`); lines.push(' ```'); } } lines.push(''); } } else { for (const ref of result.references) { lines.push(`- \`${ref.location.file}:${ref.location.line}\` [${ref.type}]`); } } // JSON output lines.push(''); lines.push('---'); lines.push('<details>'); lines.push('<summary>Full JSON Output (click to expand)</summary>'); lines.push(''); lines.push('```json'); lines.push(JSON.stringify(result, null, 2)); lines.push('```'); lines.push('</details>'); return lines.join('\n'); } /** * Format call hierarchy result for human-readable output */ function formatCallHierarchyResult(result: CallHierarchyResult): string { const lines: string[] = []; // Header lines.push(`# Call Hierarchy: "${result.query.symbolName}"`); lines.push(''); lines.push(`**Direction**: ${result.query.direction}`); lines.push(`**Max Depth**: ${result.query.maxDepth}`); lines.push(''); // Root entity info if (result.rootEntity) { lines.push('## 🎯 Root Function'); lines.push(''); lines.push(`- **Name**: \`${result.rootEntity.name}\``); lines.push(`- **Type**: ${result.rootEntity.type}`); if (result.rootEntity.filePath) { lines.push( `- **Location**: \`${result.rootEntity.filePath}:${result.rootEntity.startLine}\``, ); } if (result.rootEntity.signature) { lines.push('- **Signature**:'); lines.push('```' + (result.rootEntity.language || '')); lines.push(result.rootEntity.signature); lines.push('```'); } lines.push(''); } // ASCII Tree Visualization if (result.visualizations.asciiTree) { lines.push('---'); lines.push('## 📊 Call Hierarchy Tree'); lines.push(''); lines.push('```'); lines.push(result.visualizations.asciiTree); lines.push('```'); lines.push(''); } // Mermaid Diagram if (result.visualizations.mermaidDiagram) { lines.push('---'); lines.push('## 🔷 Mermaid Diagram'); lines.push(''); lines.push(result.visualizations.mermaidDiagram); lines.push(''); } // Statistics lines.push('---'); lines.push('## 📈 Statistics'); lines.push(''); lines.push(`- **Total Nodes**: ${result.stats.totalNodes}`); lines.push(`- **Max Depth Reached**: ${result.stats.maxDepthReached}`); lines.push(`- **Tree Pruned**: ${result.stats.pruned ? 'Yes' : 'No'}`); lines.push(`- **Build Time**: ${result.stats.buildTimeMs}ms`); lines.push(''); if (result.stats.pruned) { lines.push( '> ⚠️ **Note**: Tree was pruned to limit output size. Some branches may be truncated.', ); lines.push(''); } // JSON output lines.push('---'); lines.push('<details>'); lines.push('<summary>Full JSON Output (click to expand)</summary>'); lines.push(''); lines.push('```json'); lines.push(JSON.stringify(result, null, 2)); lines.push('```'); lines.push('</details>'); return lines.join('\n'); } /** * Format code smells detection result */ function formatSmellsResult(result: DetectSmellsResult): string { const lines: string[] = []; // Header lines.push(`# Code Smell Detection: "${result.projectPath}"`); lines.push(''); lines.push(`**Total Smells Found**: ${result.summary.totalSmells}`); lines.push(`**Entities Analyzed**: ${result.stats.entitiesAnalyzed}`); lines.push(`**Detection Time**: ${result.stats.detectionTimeMs}ms`); lines.push(''); if (result.summary.totalSmells === 0) { lines.push('---'); lines.push('✅ **No code smells detected!**'); lines.push(''); lines.push('Your code looks clean. Great job!'); return lines.join('\n'); } // Summary by severity lines.push('## 📊 Summary by Severity'); lines.push(''); const { bySeverity } = result.summary; if (bySeverity.ERROR > 0) { lines.push(`- 🔴 **ERROR**: ${bySeverity.ERROR} smells`); } if (bySeverity.WARNING > 0) { lines.push(`- 🟡 **WARNING**: ${bySeverity.WARNING} smells`); } if (bySeverity.INFO > 0) { lines.push(`- 🔵 **INFO**: ${bySeverity.INFO} smells`); } lines.push(''); // Summary by type lines.push('## 📈 Summary by Type'); lines.push(''); const { byType } = result.summary; for (const [type, count] of Object.entries(byType)) { if (count > 0) { const icon = getSmellIcon(type as SmellType); lines.push(`- ${icon} **${formatSmellType(type as SmellType)}**: ${count}`); } } lines.push(''); // Detailed smells lines.push('---'); lines.push('## 🔍 Detected Smells'); lines.push(''); // Group by severity const smellsBySeverity = { ERROR: result.smells.filter((s) => s.severity === 'ERROR'), WARNING: result.smells.filter((s) => s.severity === 'WARNING'), INFO: result.smells.filter((s) => s.severity === 'INFO'), }; for (const [severity, smells] of Object.entries(smellsBySeverity)) { if (smells.length === 0) continue; const icon = severity === 'ERROR' ? '🔴' : severity === 'WARNING' ? '🟡' : '🔵'; lines.push(`### ${icon} ${severity} (${smells.length})`); lines.push(''); for (const smell of smells) { lines.push(`#### ${getSmellIcon(smell.type)} ${smell.message}`); lines.push(''); lines.push(`**Location**: \`${smell.location.file}:${smell.location.line}\``); lines.push(`**Type**: ${formatSmellType(smell.type)}`); if (smell.metrics) { lines.push('**Metrics**:'); if (smell.metrics.complexity !== undefined) { lines.push(` - Complexity: ${smell.metrics.complexity}`); } if (smell.metrics.lineCount !== undefined) { lines.push(` - Line Count: ${smell.metrics.lineCount}`); } if (smell.metrics.parameterCount !== undefined) { lines.push(` - Parameter Count: ${smell.metrics.parameterCount}`); } if (smell.metrics.nestingDepth !== undefined) { lines.push(` - Nesting Depth: ${smell.metrics.nestingDepth}`); } } lines.push(''); lines.push(`**Explanation**: ${smell.explanation}`); lines.push(''); lines.push(`💡 **Suggestion**: ${smell.suggestion}`); lines.push(''); lines.push('---'); lines.push(''); } } // JSON output lines.push('## 📄 Full JSON Output'); lines.push(''); lines.push('<details>'); lines.push('<summary>Click to expand</summary>'); lines.push(''); lines.push('```json'); lines.push(JSON.stringify(result, null, 2)); lines.push('```'); lines.push('</details>'); return lines.join('\n'); } /** * Get icon for smell type */ function getSmellIcon(type: SmellType): string { const icons: Record<SmellType, string> = { 'god-function': '🏛️', 'deep-nesting': '🪆', 'long-parameters': '📝', 'long-function': '📏', 'dead-code': '💀', 'magic-number': '🔮', 'duplicated-code': '🔁', }; return icons[type] || '⚠️'; } /** * Format smell type for display */ function formatSmellType(type: SmellType): string { const names: Record<SmellType, string> = { 'god-function': 'God Function', 'deep-nesting': 'Deep Nesting', 'long-parameters': 'Long Parameter List', 'long-function': 'Long Function', 'dead-code': 'Dead Code', 'magic-number': 'Magic Number', 'duplicated-code': 'Duplicated Code', }; return names[type] || type; } /** * Format refactoring suggestions result */ function formatRefactoringSuggestionsResult(result: SuggestRefactoringsResult): string { const lines: string[] = []; // Header lines.push(`# Refactoring Suggestions`); lines.push(''); lines.push(`**Total Suggestions**: ${result.summary.totalSuggestions}`); lines.push(`**Analysis Time**: ${result.stats.analysisTimeMs}ms`); lines.push(''); if (result.summary.totalSuggestions === 0) { lines.push('---'); lines.push('✅ **No refactorings suggested**'); lines.push(''); lines.push('Your code looks good!'); return lines.join('\n'); } // Summary by priority lines.push('## 📊 Summary by Priority'); lines.push(''); const { byPriority } = result.summary; if (byPriority.high > 0) { lines.push(`- 🔴 **High Priority**: ${byPriority.high} suggestions`); } if (byPriority.medium > 0) { lines.push(`- 🟡 **Medium Priority**: ${byPriority.medium} suggestions`); } if (byPriority.low > 0) { lines.push(`- 🔵 **Low Priority**: ${byPriority.low} suggestions`); } lines.push(''); // Summary by pattern lines.push('## 🔧 Summary by Pattern'); lines.push(''); const { byPattern } = result.summary; for (const [pattern, count] of Object.entries(byPattern)) { if (count > 0) { const icon = getRefactoringIcon(pattern as RefactoringPattern); lines.push( `- ${icon} **${formatRefactoringPattern(pattern as RefactoringPattern)}**: ${count}`, ); } } lines.push(''); // Detailed suggestions lines.push('---'); lines.push('## 💡 Recommended Refactorings'); lines.push(''); // Group by priority const suggestionsByPriority = { high: result.suggestions.filter((s) => s.priority === 'high'), medium: result.suggestions.filter((s) => s.priority === 'medium'), low: result.suggestions.filter((s) => s.priority === 'low'), }; for (const [priority, suggestions] of Object.entries(suggestionsByPriority)) { if (suggestions.length === 0) continue; const icon = priority === 'high' ? '🔴' : priority === 'medium' ? '🟡' : '🔵'; lines.push(`### ${icon} ${priority.toUpperCase()} Priority (${suggestions.length})`); lines.push(''); for (const suggestion of suggestions) { lines.push(`#### ${getRefactoringIcon(suggestion.pattern)} ${suggestion.title}`); lines.push(''); lines.push(`**ID**: \`${suggestion.id}\``); lines.push(`**Pattern**: ${formatRefactoringPattern(suggestion.pattern)}`); lines.push( `**Target**: \`${suggestion.targetEntity.name}\` in \`${suggestion.targetEntity.filePath}\``, ); lines.push(''); lines.push(`**Description**: ${suggestion.description}`); lines.push(''); lines.push(`**Reasoning**: ${suggestion.reasoning}`); lines.push(''); // Score card lines.push('**Score Card**:'); lines.push('```'); lines.push( `Benefit: ${'█'.repeat(suggestion.score.benefit)}${'░'.repeat(10 - suggestion.score.benefit)} ${suggestion.score.benefit}/10`, ); lines.push( `Risk: ${'█'.repeat(suggestion.score.risk)}${'░'.repeat(10 - suggestion.score.risk)} ${suggestion.score.risk}/10`, ); lines.push( `Effort: ${'█'.repeat(suggestion.score.effort)}${'░'.repeat(10 - suggestion.score.effort)} ${suggestion.score.effort}/10`, ); lines.push( `Impact: ${'█'.repeat(suggestion.score.impact)}${'░'.repeat(10 - suggestion.score.impact)} ${suggestion.score.impact}/10`, ); lines.push(`Overall: ${suggestion.score.overall}/10`); lines.push('```'); lines.push(''); // Benefits lines.push('**✅ Benefits**:'); for (const benefit of suggestion.benefits) { lines.push(`- ${benefit}`); } lines.push(''); // Risks lines.push('**⚠️ Risks**:'); for (const risk of suggestion.risks) { lines.push(`- ${risk}`); } lines.push(''); // Steps lines.push('**📋 Step-by-Step Instructions**:'); for (const step of suggestion.steps) { lines.push(step); } lines.push(''); if (suggestion.estimatedTime) { lines.push(`**⏱️ Estimated Time**: ${suggestion.estimatedTime}`); lines.push(''); } if (suggestion.affectedFiles && suggestion.affectedFiles.length > 0) { lines.push(`**📁 Affected Files**: ${suggestion.affectedFiles.length} file(s)`); for (const file of suggestion.affectedFiles) { lines.push(` - \`${file}\``); } lines.push(''); } lines.push('---'); lines.push(''); } } // Next steps lines.push('## 🚀 Next Steps'); lines.push(''); lines.push('1. Review suggestions by priority (start with 🔴 High)'); lines.push('2. For each suggestion, assess benefit vs risk'); lines.push('3. Use `code.previewRefactoring` to see before/after (coming soon)'); lines.push('4. Apply refactorings incrementally'); lines.push('5. Run tests after each refactoring'); lines.push(''); return lines.join('\n'); } /** * Get icon for refactoring pattern */ function getRefactoringIcon(pattern: RefactoringPattern): string { const icons: Record<RefactoringPattern, string> = { 'extract-method': '✂️', 'introduce-parameter-object': '📦', 'remove-dead-code': '🗑️', 'inline-function': '➡️', 'rename-symbol': '✏️', 'split-function': '🔀', }; return icons[pattern] || '🔧'; } /** * Format refactoring pattern for display */ function formatRefactoringPattern(pattern: RefactoringPattern): string { const names: Record<RefactoringPattern, string> = { 'extract-method': 'Extract Method', 'introduce-parameter-object': 'Introduce Parameter Object', 'remove-dead-code': 'Remove Dead Code', 'inline-function': 'Inline Function', 'rename-symbol': 'Rename Symbol', 'split-function': 'Split Function', }; return names[pattern] || pattern; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nervusdb/nervusdb-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server