Skip to main content
Glama
references.ts5.23 kB
/** * Find References Tool - Find all references of a symbol */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { LspContext, createOneBasedPosition } from '../types.js'; import * as LspOperations from '../lsp/operations/index.js'; import { symbolPositionSchema } from './schemas.js'; import { formatCursorContext } from '../utils/cursor-context.js'; import { formatFilePath } from './utils.js'; import { enrichSymbolsWithCode, createSignaturePreview } from './enrichment.js'; import { Location } from '../types/lsp.js'; import { validateSymbolPosition } from './validation.js'; export function registerReferencesTool( server: McpServer, createContext: () => LspContext ) { server.registerTool( 'references', { title: 'References', description: 'Finds all references of a given symbol across the codebase', inputSchema: symbolPositionSchema, }, async (request) => { const ctx = createContext(); if (!ctx.client) throw new Error('LSP client not initialized'); // Validate and parse request arguments const validatedRequest = validateSymbolPosition(request); // Convert raw request to branded position type const symbolRequest = { file: validatedRequest.file, position: createOneBasedPosition( validatedRequest.line, validatedRequest.character ), }; const result = await LspOperations.findReferences(ctx, symbolRequest); if (!result.ok) throw new Error(result.error.message); // Format response with cursor context const { result: references, cursorContext } = result.data; // Format references with file grouping and signature previews const symbolName = cursorContext?.symbolName || 'symbol'; const formattedText = await formatReferencesResults( references, symbolName ); const sections: string[] = []; if (cursorContext) { sections.push(formatCursorContext(cursorContext)); } sections.push(formattedText); const finalText = sections.join('\n\n'); return { content: [ { type: 'text' as const, text: finalText, }, ], }; } ); } async function formatReferencesResults( references: Location[], symbolName: string ): Promise<string> { if (references.length === 0) { return 'Found no references'; } // Convert references to enrichable symbols format with expanded ranges for full line context const symbols = references.map((ref) => ({ name: 'reference', // References don't have symbol names, just locations kind: 1, // Use a generic kind for references location: { uri: ref.uri, range: { // Expand range to capture the full line for better context start: { line: ref.range.start.line, character: 0, // Start of line }, end: { line: ref.range.start.line, // Same line character: 1000, // End of line (will be clamped by enrichment) }, }, }, })); // Enrich symbols with code snippets for signature previews const enrichmentResults = await enrichSymbolsWithCode(symbols); const enrichedReferences = enrichmentResults.map((result, index) => ({ ...references[index], signaturePreview: result.codeSnippet ? createSignaturePreview(result.codeSnippet.trim(), 100) : null, error: result.error, })); // Group enriched references by file type EnrichedReference = Location & { signaturePreview?: string | null; error?: string | undefined; }; const groupedByFile = new Map<string, EnrichedReference[]>(); for (const ref of enrichedReferences) { const uri = ref.uri; if (!uri || !ref.range) continue; // Skip references without URI or range if (!groupedByFile.has(uri)) { groupedByFile.set(uri, []); } groupedByFile.get(uri)!.push({ uri, range: ref.range, signaturePreview: ref.signaturePreview, error: ref.error, }); } // Add summary const fileText = groupedByFile.size === 1 ? 'file' : 'files'; let result = `Found ${references.length} reference(s) across ${groupedByFile.size} ${fileText}`; // Add files with references for (const [uri, fileReferences] of groupedByFile) { const filePath = formatFilePath(uri); result += `\n\n${filePath} (${fileReferences.length} references)\n`; // Sort references by line number for natural reading order const sortedReferences = fileReferences.sort((a, b) => { const lineA = a.range.start.line; const lineB = b.range.start.line; return lineA - lineB; }); for (const ref of sortedReferences) { const line = ref.range.start.line + 1; // Convert to 1-based const char = ref.range.start.character + 1; // Convert to 1-based result += ` @${line}:${char} ${symbolName}`; // Add signature preview if available if (ref.signaturePreview) { result += `\n \`${ref.signaturePreview}\``; } else if (ref.error) { result += `\n // ${ref.error}`; } result += `\n`; } } return result.trim(); }

Latest Blog Posts

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/p1va/symbols-mcp'

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