Skip to main content
Glama
completion.ts5.04 kB
/** * Completion Tool - Get code completion suggestions at a position */ 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 { getSymbolKindName } from './utils.js'; import { CompletionResult } from '../types/lsp.js'; import { validateSymbolPosition } from './validation.js'; export function registerCompletionTool( server: McpServer, createContext: () => LspContext ) { server.registerTool( 'completion', { title: 'Completion', description: 'Gets context-aware code completions at a given position. IMPORTANT: To get meaningful results provide precise line and character pointing right after trigger characters. e.g. "client.|some_method()" returns get(), some_method(), another_method() etc.', 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.completion(ctx, symbolRequest); if (!result.ok) { throw new Error(result.error.message); } // Format response with cursor context const { result: completions, cursorContext } = result.data; const formattedText = formatCompletionResults(completions); const sections: string[] = []; if (cursorContext) { sections.push(formatCursorContext(cursorContext)); } sections.push(formattedText); return { content: [ { type: 'text' as const, text: sections.join('\n\n'), }, ], }; } ); } /** * Format completion results grouped by symbol kind with prioritization */ function formatCompletionResults(completions: CompletionResult[]): string { if (completions.length === 0) { return 'Found no completion suggestions'; } // Limit results to avoid token overflow (prioritize higher priority items) const maxResults = 100; const sortedCompletions = completions .sort((a, b) => { // Sort by sortText first (LSP priority), then by label const sortA = a.sortText || a.label; const sortB = b.sortText || b.label; return sortA.localeCompare(sortB); }) .slice(0, maxResults); // Group by symbol kind const groupedByKind = new Map<number, CompletionResult[]>(); for (const completion of sortedCompletions) { const kind = completion.kind || 1; // Default to Text if no kind if (!groupedByKind.has(kind)) { groupedByKind.set(kind, []); } groupedByKind.get(kind)!.push(completion); } // Define priority order for symbol kinds (most useful first) const kindPriority = [ 2, // Method 10, // Property 3, // Function 7, // Class 8, // Interface 9, // Module 6, // Variable 4, // Constructor 5, // Field 13, // Enum 22, // Constant 1, // Text (fallback) ]; let result = `Found ${completions.length} completion suggestion${completions.length === 1 ? '' : 's'}`; if (completions.length > maxResults) { result += ` (showing top ${maxResults})`; } // Add grouped results in priority order for (const kind of kindPriority) { const items = groupedByKind.get(kind); if (!items || items.length === 0) continue; const kindName = getSymbolKindName(kind); result += `\n\n${kindName}s (${items.length})`; for (const item of items.slice(0, 20)) { // Limit per group result += `\n ${item.label}`; // Add detail if available and different from label if ( item.detail && item.detail.trim() !== item.label && item.detail.trim() !== '' ) { result += ` - ${item.detail.trim()}`; } // Add brief documentation if available if ( item.documentation && typeof item.documentation === 'string' && item.documentation.trim() !== '' ) { const doc = item.documentation.trim(); // Show first line of documentation, truncated const firstLine = doc.split('\n')[0]; if (firstLine && firstLine.length > 60) { result += `\n // ${firstLine.substring(0, 57)}...`; } else if (firstLine && firstLine.length > 0) { result += `\n // ${firstLine}`; } } } if (items.length > 20) { result += `\n ... and ${items.length - 20} more`; } } return result; }

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