Skip to main content
Glama
server.ts12.3 kB
import express from "express"; import * as vscode from 'vscode'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { Server } from 'http'; import { Request, Response } from 'express'; import { registerFileTools, FileListingCallback } from './tools/file-tools'; import { registerEditTools } from './tools/edit-tools'; import { registerShellTools } from './tools/shell-tools'; import { registerDiagnosticsTools } from './tools/diagnostics-tools'; import { registerSymbolTools } from './tools/symbol-tools'; import { logger } from './utils/logger'; export interface ToolConfiguration { file: boolean; edit: boolean; shell: boolean; diagnostics: boolean; symbol: boolean; } export class MCPServer { private server: McpServer; private transport: StreamableHTTPServerTransport; private app: express.Application; private httpServer?: Server; private port: number; private host: string; private fileListingCallback?: FileListingCallback; private terminal?: vscode.Terminal; private toolConfig: ToolConfiguration; public setFileListingCallback(callback: FileListingCallback) { this.fileListingCallback = callback; } constructor(port: number = 3000, host: string = '127.0.0.1', terminal?: vscode.Terminal, toolConfig?: ToolConfiguration) { this.port = port; this.host = host; this.terminal = terminal; this.toolConfig = toolConfig || { file: true, edit: true, shell: true, diagnostics: true, symbol: true }; this.app = express(); this.app.use(express.json()); // Initialize MCP Server this.server = new McpServer({ name: "vscode-mcp-server", version: "1.0.0", }, { capabilities: { logging: {}, tools: { listChanged: false } } }); // Initialize transport this.transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); // Note: setupTools() is no longer called here this.setupRoutes(); this.setupEventHandlers(); } public setupTools(): void { // Register tools from the tools module based on configuration if (this.fileListingCallback) { logger.info(`Setting up MCP tools with configuration: ${JSON.stringify(this.toolConfig)}`); // Register file tools if enabled if (this.toolConfig.file) { registerFileTools(this.server, this.fileListingCallback); logger.info('MCP file tools registered successfully'); } else { logger.info('MCP file tools disabled by configuration'); } // Register edit tools if enabled if (this.toolConfig.edit) { registerEditTools(this.server); logger.info('MCP edit tools registered successfully'); } else { logger.info('MCP edit tools disabled by configuration'); } // Register shell tools if enabled if (this.toolConfig.shell) { registerShellTools(this.server, this.terminal); logger.info('MCP shell tools registered successfully'); } else { logger.info('MCP shell tools disabled by configuration'); } // Register diagnostics tools if enabled if (this.toolConfig.diagnostics) { registerDiagnosticsTools(this.server); logger.info('MCP diagnostics tools registered successfully'); } else { logger.info('MCP diagnostics tools disabled by configuration'); } // Register symbol tools if enabled if (this.toolConfig.symbol) { registerSymbolTools(this.server); logger.info('MCP symbol tools registered successfully'); } else { logger.info('MCP symbol tools disabled by configuration'); } } else { logger.warn('File listing callback not set during tools setup'); } } private setupRoutes(): void { // Handle POST requests for client-to-server communication this.app.post('/mcp', async (req, res) => { logger.info(`Request received: ${req.method} ${req.url}`); try { await this.transport.handleRequest(req, res, req.body); } catch (error) { logger.error(`Error handling MCP request: ${error instanceof Error ? error.message : String(error)}`); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); // Handle SSE endpoint for server-to-client streaming this.app.get('/mcp/sse', async (req, res) => { logger.info('Received SSE connection request'); try { await this.transport.handleRequest(req, res, undefined); } catch (error) { logger.error(`Error handling SSE request: ${error instanceof Error ? error.message : String(error)}`); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); // Handle unsupported methods this.app.get('/mcp', async (req, res) => { logger.info('Received GET MCP request'); res.writeHead(405).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null })); }); this.app.delete('/mcp', async (req, res) => { logger.info('Received DELETE MCP request'); res.writeHead(405).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null })); }); // Handle OPTIONS requests for CORS this.app.options('/mcp', (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept'); res.status(204).end(); }); } private setupEventHandlers(): void { // Log HTTP server events if (this.httpServer) { this.httpServer.on('error', (error: Error) => { logger.error(`[Server] HTTP Server Error: ${error.message}`); }); this.httpServer.on('listening', () => { logger.info(`[Server] HTTP Server ready`); }); this.httpServer.on('close', () => { logger.info(`[Server] HTTP Server closed`); }); } } public async start(): Promise<void> { try { logger.info('[MCPServer.start] Starting MCP server'); const startTime = Date.now(); // Connect transport before starting server logger.info('[MCPServer.start] Connecting transport'); const transportConnectStart = Date.now(); await this.server.connect(this.transport); const transportConnectTime = Date.now() - transportConnectStart; logger.info(`[MCPServer.start] Transport connected (took ${transportConnectTime}ms)`); // Start HTTP server logger.info('[MCPServer.start] Starting HTTP server'); const httpServerStartTime = Date.now(); return new Promise((resolve) => { // Bind to localhost only for security this.httpServer = this.app.listen(this.port, this.host, () => { const httpStartTime = Date.now() - httpServerStartTime; logger.info(`[MCPServer.start] HTTP Server started (took ${httpStartTime}ms)`); logger.info(`MCP Server listening on ${this.host}:${this.port}`); const totalTime = Date.now() - startTime; logger.info(`[MCPServer.start] Server startup complete (total: ${totalTime}ms)`); resolve(); }); }); } catch (error) { logger.error(`[MCPServer.start] Failed to start MCP Server: ${error instanceof Error ? error.message : String(error)}`); throw error; } } public async stop(forceTimeout: number = 5000): Promise<void> { logger.info('[MCPServer.stop] Starting server shutdown process'); const stopStartTime = Date.now(); try { // Close HTTP server with timeout if (this.httpServer) { logger.info('[MCPServer.stop] Closing HTTP server (with timeout)'); const httpServerCloseStart = Date.now(); await Promise.race([ // Normal close operation new Promise<void>((resolve, reject) => { this.httpServer!.close((err) => { const httpCloseTime = Date.now() - httpServerCloseStart; if (err) { logger.error(`[MCPServer.stop] HTTP server closed with error: ${err.message} (took ${httpCloseTime}ms)`); reject(err); } else { logger.info(`[MCPServer.stop] HTTP server closed successfully (took ${httpCloseTime}ms)`); resolve(); } }); }), // Timeout fallback new Promise<void>((resolve) => { setTimeout(() => { logger.warn(`[MCPServer.stop] HTTP server close timed out after ${forceTimeout}ms - forcing close`); // We resolve anyway to continue with the shutdown process resolve(); }, forceTimeout); }) ]); } // Rest of the shutdown process... logger.info('[MCPServer.stop] Closing transport'); const transportCloseStart = Date.now(); await this.transport.close(); const transportCloseTime = Date.now() - transportCloseStart; logger.info(`[MCPServer.stop] Transport closed (took ${transportCloseTime}ms)`); logger.info('[MCPServer.stop] Closing MCP server'); const serverCloseStart = Date.now(); await this.server.close(); const serverCloseTime = Date.now() - serverCloseStart; logger.info(`[MCPServer.stop] MCP server closed (took ${serverCloseTime}ms)`); const totalStopTime = Date.now() - stopStartTime; logger.info(`[MCPServer.stop] MCP Server shutdown complete (total: ${totalStopTime}ms)`); } catch (error) { logger.error(`[MCPServer.stop] Error during server shutdown: ${error instanceof Error ? error.message : String(error)}`); throw error; } } }

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/juehang/vscode-mcp-server'

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