Skip to main content
Glama

Token Saver MCP

by jerry426
extension.ts23.9 kB
import type { Server } from 'node:http' import cors from 'cors' import express from 'express' import * as vscode from 'vscode' let server: Server | undefined let requestCounter = 0 const responseBuffers = new Map<string, any>() // Commands that require workspace const WORKSPACE_COMMANDS = [ 'workbench.action.tasks.runTask', 'workbench.action.debug.start', 'vscode.executeWorkspaceSymbolProvider', ] // Commands that require active editor const EDITOR_COMMANDS = [ 'editor.action.formatDocument', 'editor.action.organizeImports', 'vscode.executeDefinitionProvider', 'vscode.executeHoverProvider', ] export function activate(context: vscode.ExtensionContext) { const app = express() app.use(express.json({ limit: '50mb' })) app.use(cors()) const outputChannel = vscode.window.createOutputChannel('VSCode Internals Gateway') // Request logging middleware app.use((req: express.Request & { id?: number, startTime?: number }, res: express.Response, next: express.NextFunction) => { req.id = ++requestCounter req.startTime = Date.now() outputChannel.appendLine(`[${req.id}] ${new Date().toISOString()} ${req.method} ${req.path}`) // Log response when finished const originalSend = res.send res.send = function (data: any) { const duration = req.startTime ? Date.now() - req.startTime : 0 outputChannel.appendLine(`[${req.id}] Completed in ${duration}ms`) return originalSend.call(this, data) } next() }) // Health check endpoint with enhanced info app.get('/health', (_req: express.Request, res: express.Response) => { res.json({ status: 'healthy', version: '2.1.0', vscodeVersion: vscode.version, uptime: process.uptime(), requestsServed: requestCounter, workspace: vscode.workspace.name || 'No workspace', workspaceFolders: vscode.workspace.workspaceFolders?.length || 0, activeEditor: !!vscode.window.activeTextEditor, bufferedResponses: responseBuffers.size, }) }) // Universal command executor with robustness app.post('/execute-command', async (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id try { const { command, args = [], timeout = 30000 } = req.body // Validate command if (!command || typeof command !== 'string') { outputChannel.appendLine(`[${requestId}] ERROR: Invalid command parameter`) return res.status(400).json({ success: false, error: 'Invalid command parameter: must be a string', requestId, }) } outputChannel.appendLine(`[${requestId}] Executing: ${command}`) // Check workspace requirement if (WORKSPACE_COMMANDS.some(cmd => command.includes(cmd))) { if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { return res.status(400).json({ success: false, error: 'This command requires a workspace to be open', command, requestId, }) } } // Check editor requirement if (EDITOR_COMMANDS.some(cmd => command.includes(cmd))) { if (!vscode.window.activeTextEditor) { return res.status(400).json({ success: false, error: 'This command requires an active text editor', command, requestId, }) } } // Process arguments - parse file URIs and positions const processedArgs = args.map((arg: any) => { if (typeof arg === 'string' && arg.startsWith('file://')) { return vscode.Uri.parse(arg) } // Convert position objects to vscode.Position if (arg && typeof arg === 'object' && 'line' in arg && 'character' in arg && !('start' in arg)) { return new vscode.Position(arg.line, arg.character) } // Convert range objects to vscode.Range if (arg && typeof arg === 'object' && 'start' in arg && 'end' in arg) { return new vscode.Range( new vscode.Position(arg.start.line, arg.start.character), new vscode.Position(arg.end.line, arg.end.character), ) } return arg }) // Execute with timeout let timeoutHandle: NodeJS.Timeout | undefined const timeoutPromise = new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject(new Error(`Command execution timeout after ${timeout}ms`)) }, timeout) }) const commandPromise = vscode.commands.executeCommand(command, ...processedArgs) try { let result = await Promise.race([commandPromise, timeoutPromise]) if (timeoutHandle) clearTimeout(timeoutHandle) // Special handling for hover results to extract MarkdownString content if (command === 'vscode.executeHoverProvider' && Array.isArray(result)) { result = result.map((hover: any) => { if (hover && hover.contents) { return { ...hover, contents: hover.contents.map((content: any) => { // Handle different content types if (content && typeof content === 'object') { // Try to extract the value property (MarkdownString objects have this) let value = null // Try direct property access try { value = content.value || content._value } catch (e) {} // Try Reflect.get as fallback if (!value) { try { value = Reflect.get(content, 'value') || Reflect.get(content, '_value') } catch (e) {} } if (value) { return { kind: 'markdown', value: String(value), } } } // Handle plain strings if (typeof content === 'string') { return content } // Return empty object if we can't extract content return {} }), } } return hover }) } // Check response size const resultStr = JSON.stringify(result) if (resultStr.length > 1000000) { // 1MB threshold // Buffer large response const bufferId = `buffer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}` responseBuffers.set(bufferId, result) // Auto-cleanup after 5 minutes setTimeout(() => { responseBuffers.delete(bufferId) outputChannel.appendLine(`[${requestId}] Buffer ${bufferId} expired`) }, 5 * 60 * 1000) outputChannel.appendLine(`[${requestId}] Large response buffered: ${bufferId} (${resultStr.length} bytes)`) res.json({ success: true, buffered: true, bufferId, size: resultStr.length, message: 'Response too large, stored in buffer. Use /buffer/:id to retrieve.', requestId, }) } else { res.json({ success: true, result, requestId, }) } } catch (timeoutError: any) { if (timeoutHandle) clearTimeout(timeoutHandle) outputChannel.appendLine(`[${requestId}] ERROR: ${timeoutError.message}`) res.status(408).json({ success: false, error: timeoutError.message, requestId, }) } } catch (error: any) { outputChannel.appendLine(`[${requestId}] ERROR: ${error.message}`) res.status(500).json({ success: false, error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined, requestId, }) } }) // Retrieve buffered responses app.get('/buffer/:id', (req: express.Request & { id?: number }, res: express.Response) => { const bufferId = req.params.id const requestId = req.id if (!responseBuffers.has(bufferId)) { outputChannel.appendLine(`[${requestId}] Buffer not found: ${bufferId}`) return res.status(404).json({ success: false, error: 'Buffer not found or expired', bufferId, requestId, }) } const data = responseBuffers.get(bufferId) responseBuffers.delete(bufferId) // One-time retrieval outputChannel.appendLine(`[${requestId}] Retrieved buffer: ${bufferId}`) res.json({ success: true, result: data, bufferId, requestId, }) }) // Direct Language API access with validation app.post('/languages-api', async (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id try { const { method, args = [] } = req.body if (!method || typeof method !== 'string') { return res.status(400).json({ success: false, error: 'Invalid method parameter', requestId, }) } outputChannel.appendLine(`[${requestId}] Languages API: ${method}`) let result: any switch (method) { case 'getDiagnostics': result = args[0] ? vscode.languages.getDiagnostics(vscode.Uri.parse(args[0])) : vscode.languages.getDiagnostics() break case 'getLanguages': result = await vscode.languages.getLanguages() break case 'match': const [selector, document] = args result = vscode.languages.match(selector, document) break default: throw new Error(`Unknown languages API method: ${method}`) } res.json({ success: true, result, requestId, }) } catch (error: any) { outputChannel.appendLine(`[${requestId}] ERROR: ${error.message}`) res.status(500).json({ success: false, error: error.message, requestId, }) } }) // Workspace API access with validation app.post('/workspace-api', async (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id try { const { method, args = [] } = req.body if (!method || typeof method !== 'string') { return res.status(400).json({ success: false, error: 'Invalid method parameter', requestId, }) } outputChannel.appendLine(`[${requestId}] Workspace API: ${method}`) // Check if workspace is available if (!vscode.workspace.workspaceFolders && method !== 'getConfiguration') { return res.status(400).json({ success: false, error: 'No workspace open', method, requestId, }) } let result: any switch (method) { case 'findFiles': result = await vscode.workspace.findFiles(args[0], args[1], args[2]) break case 'findTextInFiles': // Handle the callback-based findTextInFiles API const [searchQuery, searchOptions = {}] = args const searchResults: any[] = [] // Process include/exclude patterns const processedOptions: any = { maxResults: searchOptions.maxResults || 100, } // Handle include patterns if (searchOptions.include && vscode.workspace.workspaceFolders?.[0]) { const patterns = Array.isArray(searchOptions.include) ? searchOptions.include : [searchOptions.include] processedOptions.include = new vscode.RelativePattern( vscode.workspace.workspaceFolders[0], patterns.length > 1 ? `{${patterns.join(',')}}` : patterns[0], ) } // Handle exclude patterns if (searchOptions.exclude) { const patterns = Array.isArray(searchOptions.exclude) ? searchOptions.exclude : [searchOptions.exclude] processedOptions.exclude = patterns.length > 1 ? `{${patterns.join(',')}}` : patterns[0] } else { // Default excludes processedOptions.exclude = '**/node_modules/**' } // Create a promise that collects all results await new Promise<void>((resolve, reject) => { try { // @ts-ignore - findTextInFiles may not be available in older VSCode versions if (!vscode.workspace.findTextInFiles) { resolve() return } // @ts-ignore vscode.workspace.findTextInFiles( searchQuery, processedOptions, (result: any) => { // Callback is called for each result if (result && result.uri && result.ranges) { searchResults.push({ uri: result.uri.toString(), ranges: result.ranges.map((r: any) => ({ start: { line: r.start.line, character: r.start.character }, end: { line: r.end.line, character: r.end.character }, })), preview: result.preview?.text || '', }) } }, // Token for cancellation (not used here) undefined, ).then( () => resolve(), (error: any) => reject(error), ) } catch (error) { reject(error) } }) result = searchResults break case 'openTextDocument': const doc = await vscode.workspace.openTextDocument(vscode.Uri.parse(args[0])) result = { uri: doc.uri.toString(), languageId: doc.languageId, version: doc.version, lineCount: doc.lineCount, } break case 'getConfiguration': result = vscode.workspace.getConfiguration(args[0]).get(args[1]) break case 'getWorkspaceFolders': result = vscode.workspace.workspaceFolders?.map(f => ({ uri: f.uri.toString(), name: f.name, index: f.index, })) break default: throw new Error(`Unknown workspace API method: ${method}`) } res.json({ success: true, result, requestId, }) } catch (error: any) { outputChannel.appendLine(`[${requestId}] ERROR: ${error.message}`) res.status(500).json({ success: false, error: error.message, requestId, }) } }) // Window API access app.post('/window-api', async (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id try { const { method, args = [] } = req.body if (!method || typeof method !== 'string') { return res.status(400).json({ success: false, error: 'Invalid method parameter', requestId, }) } outputChannel.appendLine(`[${requestId}] Window API: ${method}`) let result: any switch (method) { case 'showInformationMessage': result = await vscode.window.showInformationMessage(args[0], ...args.slice(1)) break case 'showErrorMessage': result = await vscode.window.showErrorMessage(args[0], ...args.slice(1)) break case 'showWarningMessage': result = await vscode.window.showWarningMessage(args[0], ...args.slice(1)) break case 'setStatusBarMessage': vscode.window.setStatusBarMessage(args[0], args[1]) result = true break case 'getActiveTextEditor': const editor = vscode.window.activeTextEditor result = editor ? { document: { uri: editor.document.uri.toString(), languageId: editor.document.languageId, lineCount: editor.document.lineCount, }, selection: editor.selection, viewColumn: editor.viewColumn, } : null break default: throw new Error(`Unknown window API method: ${method}`) } res.json({ success: true, result, requestId, }) } catch (error: any) { outputChannel.appendLine(`[${requestId}] ERROR: ${error.message}`) res.status(500).json({ success: false, error: error.message, requestId, }) } }) // Extension API access app.post('/extension-api', async (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id try { const { method, args = [] } = req.body if (!method || typeof method !== 'string') { return res.status(400).json({ success: false, error: 'Invalid method parameter', requestId, }) } outputChannel.appendLine(`[${requestId}] Extension API: ${method}`) let result: any switch (method) { case 'getExtension': const ext = vscode.extensions.getExtension(args[0]) result = ext ? { id: ext.id, extensionPath: ext.extensionPath, isActive: ext.isActive, packageJSON: ext.packageJSON, } : null break case 'getAllExtensions': result = vscode.extensions.all.map(ext => ({ id: ext.id, extensionPath: ext.extensionPath, isActive: ext.isActive, packageJSON: ext.packageJSON, })) break default: throw new Error(`Unknown extension API method: ${method}`) } res.json({ success: true, result, requestId, }) } catch (error: any) { outputChannel.appendLine(`[${requestId}] ERROR: ${error.message}`) res.status(500).json({ success: false, error: error.message, requestId, }) } }) // Clear buffers endpoint app.post('/admin/clear-buffers', (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id const count = responseBuffers.size responseBuffers.clear() outputChannel.appendLine(`[${requestId}] Cleared ${count} buffers`) res.json({ success: true, message: `Cleared ${count} buffers`, requestId, }) }) // List buffers endpoint app.get('/admin/buffers', (req: express.Request & { id?: number }, res: express.Response) => { const requestId = req.id const buffers = Array.from(responseBuffers.keys()).map(id => ({ id, size: JSON.stringify(responseBuffers.get(id)).length, })) res.json({ success: true, buffers, count: buffers.length, requestId, }) }) // Start server with retry logic and better error handling // Check for port override file first let startPort = 9600 // Default port if (vscode.workspace.workspaceFolders?.[0]) { const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath // eslint-disable-next-line ts/no-require-imports const portFile = require('node:path').join(workspaceRoot, '.vscode_gateway_port') try { // eslint-disable-next-line ts/no-require-imports const fs = require('node:fs') if (fs.existsSync(portFile)) { const fileContent = fs.readFileSync(portFile, 'utf8').trim() const filePort = Number.parseInt(fileContent) if (!Number.isNaN(filePort) && filePort > 0 && filePort < 65536) { startPort = filePort outputChannel.appendLine(`Using port ${startPort} from .vscode_gateway_port file`) } else { outputChannel.appendLine(`Invalid port in .vscode_gateway_port file: ${fileContent}, using default ${startPort}`) } } } catch (error: any) { outputChannel.appendLine(`Error reading .vscode_gateway_port file: ${error.message}, using default ${startPort}`) } } const maxRetries = 10 let currentPort = startPort let retries = 0 const tryListen = () => { server = app.listen(currentPort, '127.0.0.1', () => { outputChannel.appendLine(`VSCode Internals Gateway listening on port ${currentPort}`) outputChannel.show(true) // Write the actual port back to .vscode_gateway_port file for MCP server coordination if (vscode.workspace.workspaceFolders?.[0]) { const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath // eslint-disable-next-line ts/no-require-imports const portFile = require('node:path').join(workspaceRoot, '.vscode_gateway_port') try { // eslint-disable-next-line ts/no-require-imports const fs = require('node:fs') fs.writeFileSync(portFile, currentPort.toString()) outputChannel.appendLine(`Updated .vscode_gateway_port with actual port: ${currentPort}`) } catch (error: any) { outputChannel.appendLine(`Warning: Could not write .vscode_gateway_port file: ${error.message}`) } } // Show status message with context about port selection const portMessage = currentPort === startPort ? `VSCode Internals Gateway started on port ${currentPort}` : `VSCode Internals Gateway started on port ${currentPort} (auto-selected due to conflict)` vscode.window.showInformationMessage(portMessage) // Store the actual port in global state context.globalState.update('gatewayPort', currentPort) }).on('error', (err: any) => { if (err.code === 'EADDRINUSE' && retries < maxRetries) { retries++ currentPort++ outputChannel.appendLine(`Port ${currentPort - 1} in use, trying ${currentPort}...`) setTimeout(tryListen, 100) } else { outputChannel.appendLine(`Failed to start: ${err.message}`) vscode.window.showErrorMessage(`Failed to start VSCode Internals Gateway: ${err.message}`) } }) // Handle server crashes if (server) { server.on('close', () => { outputChannel.appendLine('Server closed unexpectedly') if (context.subscriptions.length > 0) { // Still active outputChannel.appendLine('Attempting to restart...') setTimeout(tryListen, 1000) } }) } } tryListen() // Register commands const showPortCommand = vscode.commands.registerCommand('vscode-internals-gateway.showPort', () => { vscode.window.showInformationMessage(`VSCode Internals Gateway is running on port ${currentPort}`) }) const showLogsCommand = vscode.commands.registerCommand('vscode-internals-gateway.showLogs', () => { outputChannel.show() }) const restartCommand = vscode.commands.registerCommand('vscode-internals-gateway.restart', () => { outputChannel.appendLine('Manual restart requested') if (server) { server.close(() => { tryListen() }) } }) context.subscriptions.push(showPortCommand, showLogsCommand, restartCommand) } export function deactivate() { if (server) { server.close() } responseBuffers.clear() }

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/jerry426/token-saver-mcp'

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