/** * VSCode LSP Tool - Provides LSP-like code intelligence via VSCode Bridge Extension * * Features: * - get_definition: Find symbol definition * - get_references: Find all symbol references * - get_hover: Get hover information for symbols * - get_document_symbols: List all symbols in file * * Requirements: * - ccw-vscode-bridge extension must be running in VSCode * - File must be open/accessible in VSCode workspace */ import { z } from 'zod'; import type { ToolSchema, ToolResult } from '../types/tool.js'; // VSCode Bridge configuration const BRIDGE_URL = 'http://127.0.0.1:3457'; const REQUEST_TIMEOUT = 10000; // 10 seconds // Define Zod schema for validation const ParamsSchema = z.object({ action: z.enum(['get_definition', 'get_references', 'get_hover', 'get_document_symbols']), file_path: z.string().describe('Absolute path to the file'), line: z.number().optional().describe('Line number (1-based)'), character: z.number().optional().describe('Character position (1-based)'), }); type Params = z.infer; /** * Result types from VSCode LSP */ interface Position { line: number; character: number; } interface Range { start: Position; end: Position; } interface Location { uri: string; range: Range; } interface HoverContent { contents: string[]; range?: Range; } interface DocumentSymbol { name: string; kind: string; range: Range; selectionRange: Range; detail?: string; parent?: string; } type LSPResult = { success: boolean; result?: Location | Location[] | HoverContent[] | DocumentSymbol[]; error?: string; action: string; }; /** * Execute HTTP request to VSCode Bridge */ async function callVSCodeBridge( endpoint: string, payload: Record ): Promise { const url = `${BRIDGE_URL}${endpoint}`; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorBody = await response.json() as any; return { success: false, error: errorBody.error || `HTTP ${response.status}: ${response.statusText}`, action: endpoint, }; } const data = await response.json() as any; return { success: data.success !== false, result: data.result, action: endpoint, }; } catch (err) { const error = err as Error; if (error.name === 'AbortError') { return { success: false, error: `Request timed out after ${REQUEST_TIMEOUT}ms`, action: endpoint, }; } if ((error as any).code === 'ECONNREFUSED') { return { success: false, error: `Could not connect to VSCode Bridge at ${BRIDGE_URL}. Is the ccw-vscode-bridge extension running in VSCode?`, action: endpoint, }; } return { success: false, error: `Request failed: ${error.message}`, action: endpoint, }; } } /** * Handler: get_definition */ async function handleGetDefinition(params: Params): Promise { if (params.line === undefined || params.character === undefined) { return { success: false, error: 'line and character are required for get_definition', action: 'get_definition', }; } return callVSCodeBridge('/get_definition', { file_path: params.file_path, line: params.line, character: params.character, }); } /** * Handler: get_references */ async function handleGetReferences(params: Params): Promise { if (params.line === undefined || params.character === undefined) { return { success: false, error: 'line and character are required for get_references', action: 'get_references', }; } return callVSCodeBridge('/get_references', { file_path: params.file_path, line: params.line, character: params.character, }); } /** * Handler: get_hover */ async function handleGetHover(params: Params): Promise { if (params.line === undefined || params.character === undefined) { return { success: false, error: 'line and character are required for get_hover', action: 'get_hover', }; } return callVSCodeBridge('/get_hover', { file_path: params.file_path, line: params.line, character: params.character, }); } /** * Handler: get_document_symbols */ async function handleGetDocumentSymbols(params: Params): Promise { return callVSCodeBridge('/get_document_symbols', { file_path: params.file_path, }); } /** * Main handler function */ export async function handler(params: Record): Promise> { try { // Validate parameters const validatedParams = ParamsSchema.parse(params); // Route to appropriate handler based on action let result: LSPResult; switch (validatedParams.action) { case 'get_definition': result = await handleGetDefinition(validatedParams); break; case 'get_references': result = await handleGetReferences(validatedParams); break; case 'get_hover': result = await handleGetHover(validatedParams); break; case 'get_document_symbols': result = await handleGetDocumentSymbols(validatedParams); break; default: return { success: false, error: `Unknown action: ${(validatedParams as any).action}`, result: null as any, }; } if (!result.success) { return { success: false, error: result.error || 'Unknown error', result: null as any, }; } return { success: true, result, }; } catch (err) { if (err instanceof z.ZodError) { return { success: false, error: `Parameter validation failed: ${err.issues.map((e: z.ZodIssue) => `${e.path.join('.')}: ${e.message}`).join(', ')}`, result: null as any, }; } return { success: false, error: `Execution failed: ${(err as Error).message}`, result: null as any, }; } } /** * Tool schema for MCP */ export const schema: ToolSchema = { name: 'vscode_lsp', description: `Access live VSCode LSP features via ccw-vscode-bridge extension. **Actions:** - get_definition: Find the definition of a symbol at a given position - get_references: Find all references to a symbol at a given position - get_hover: Get hover information for a symbol at a given position - get_document_symbols: List all symbols in a given file **Usage Examples:** Find definition: vscode_lsp(action="get_definition", file_path="/path/to/file.ts", line=10, character=5) Find references: vscode_lsp(action="get_references", file_path="/path/to/file.py", line=42, character=15) Get hover info: vscode_lsp(action="get_hover", file_path="/path/to/file.go", line=100, character=20) List document symbols: vscode_lsp(action="get_document_symbols", file_path="/path/to/file.rs") **Requirements:** - ccw-vscode-bridge extension must be installed and active in VSCode - File must be part of the VSCode workspace - VSCode Bridge HTTP server running on http://127.0.0.1:3457`, inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['get_definition', 'get_references', 'get_hover', 'get_document_symbols'], description: 'LSP action to perform', }, file_path: { type: 'string', description: 'Absolute path to the file (required)', }, line: { type: 'number', description: 'Line number (1-based, required for definition/references/hover)', }, character: { type: 'number', description: 'Character position (1-based, required for definition/references/hover)', }, }, required: ['action', 'file_path'], }, };