Skip to main content
Glama
mcp-server.ts4.15 kB
/** * Native MCP Server Implementation * * This replaces the bridge scripts by directly implementing the MCP protocol * using the official @modelcontextprotocol/sdk package. */ import type { Application } from "express"; import type { ExpressMCP } from "./index"; interface MCPServerOptions { port?: number; url?: string; debug?: boolean; expressMcp?: ExpressMCP; } interface MCPTool { name: string; title: string; description: string; inputSchema?: unknown; outputSchema?: unknown; } interface MCPResponse { ok: boolean; result?: unknown; error?: string; status?: number; } class McpError extends Error { constructor(message: string) { super(message); this.name = "McpError"; } } export class MCPServer { private baseUrl: string; private debug: boolean; private tools: MCPTool[] = []; private expressMcp?: ExpressMCP; constructor(options: MCPServerOptions = {}) { this.baseUrl = options.url || "http://localhost:3000"; this.debug = options.debug || false; this.expressMcp = options.expressMcp; } async start(): Promise<void> { this.tools = await this.loadTools(); this.log("MCP Server started with", this.tools.length, "tools"); } private log(...args: unknown[]) { if (this.debug) { console.error("[MCP Server]", ...args); } } private async loadTools(): Promise<MCPTool[]> { if (this.expressMcp) { // Load tools from local Express app return this.expressMcp.listTools() as MCPTool[]; } // Load tools from remote Express app try { const response = await fetch(`${this.baseUrl}/mcp/tools`); const data = await response.json(); return data.tools || []; } catch (error) { this.log("Failed to load tools:", error); return []; } } private async invokeTool(toolName: string, args: unknown): Promise<unknown> { if (this.expressMcp) { // Invoke tool directly through Express app const dispatcher = ( this.expressMcp as unknown as { dispatcher: { dispatch: ( method: string, path: string, args: unknown, headers: Record<string, string>, ) => Promise<MCPResponse>; }; } ).dispatcher; const tool = this.tools.find( (t) => t.name === toolName || t.title === toolName, ); if (!tool) { throw new McpError(`Tool '${toolName}' not found`); } // This is a simplified implementation - in practice, you'd need to map tool names to routes const result = await dispatcher.dispatch("POST", "/mcp/invoke", args, { "content-type": "application/json", }); return result; } // Invoke tool through remote Express app return this.makeRequest("POST", "/mcp/invoke", { toolName, args, }); } private async makeRequest( method: string, path: string, data?: unknown, ): Promise<unknown> { const url = new URL(path, this.baseUrl); try { const response = await fetch(url.toString(), { method, headers: { "Content-Type": "application/json", }, body: data ? JSON.stringify(data) : undefined, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { this.log("Request failed:", error); throw error; } } async handleRequest(request: { method: string; params: unknown; }): Promise<unknown> { try { switch (request.method) { case "tools/list": return { tools: this.tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, })), }; case "tools/call": { const { name, arguments: args } = request.params as { name: string; arguments: unknown; }; const result = await this.invokeTool(name, args); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } default: throw new McpError(`Unknown method: ${request.method}`); } } catch (error) { this.log("Request handling error:", error); if (error instanceof McpError) { throw error; } throw new McpError( error instanceof Error ? error.message : "Unknown error", ); } } }

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/bowen31337/expressjs_mcp'

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