Skip to main content
Glama
index.js10.8 kB
#!/usr/bin/env node import express from "express"; import cors from "cors"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { readFile } from "fs/promises"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const app = express(); const PORT = process.env.PORT || 3000; // Session management for SSE connections const sessions = new Map(); // Middleware app.use( cors({ origin: "*", methods: ["POST", "OPTIONS", "GET", "DELETE"], allowedHeaders: [ "mcp-session-id", "Content-Type", "Accept", "mcp-session-id", "mcp-protocol-version", ], exposedHeaders: ["mcp-session-id", "mcp-protocol-version"], credentials: true, }) ); app.use(express.json()); // Handle OPTIONS preflight for all routes // app.options("*", cors()); // Resource paths const WIDGET_RESOURCE_PATH = join(__dirname, "resources", "widgetResource.md"); const PAGE_RESOURCE_PATH = join(__dirname, "resources", "pageResource.md"); // Tool handlers async function handleReadWidget() { try { const content = await readFile(WIDGET_RESOURCE_PATH, "utf-8"); return { content: [ { type: "text", text: content, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error reading widget resource: ${error.message}`, }, ], isError: true, }; } } async function handleReadPage() { try { const content = await readFile(PAGE_RESOURCE_PATH, "utf-8"); return { content: [ { type: "text", text: content, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error reading page resource: ${error.message}`, }, ], isError: true, }; } } // Create MCP Server instance function createMCPServer() { const server = new Server( { name: "mcp-demo-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Register tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "read_widget_resource", description: "Reads the content of widgetResource.md file. This file contains widget-related information and documentation.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "read_page_resource", description: "Reads the content of pageResource.md file. This file contains page-related information and documentation.", inputSchema: { type: "object", properties: {}, required: [], }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "read_widget_resource": return await handleReadWidget(); case "read_page_resource": return await handleReadPage(); default: return { content: [ { type: "text", text: `Unknown tool: ${name}`, }, ], isError: true, }; } }); return server; } // HTTP Streaming endpoint (stateless MCP) app.post("/mcp", async (req, res) => { console.log("HTTP streaming request received"); try { // Create a new MCP server instance for this request const server = createMCPServer(); // Create HTTP streaming transport in stateless mode const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // Stateless mode }); res.on("close", () => { transport.close(); }); // Connect server to transport await server.connect(transport); // Handle the request await transport.handleRequest(req, res, req.body); console.log("HTTP streaming request processed successfully"); } catch (error) { console.error("Error in HTTP streaming endpoint:", error); if (!res.headersSent) { res.status(500).json({ error: error.message }); } } }); // Also support GET for SSE-style streaming // app.get("/mcp", async (req, res) => { // console.log("HTTP streaming GET request received"); // try { // // Create a new MCP server instance for this request // const server = createMCPServer(); // // Create HTTP streaming transport in stateless mode // const transport = new StreamableHTTPServerTransport({ // sessionIdGenerator: undefined, // Stateless mode // }); // // Connect server to transport // await server.connect(transport); // // Handle the GET request (for SSE streaming) // await transport.handleRequest(req, res); // console.log("HTTP streaming GET request processed successfully"); // } catch (error) { // console.error("Error in HTTP streaming GET endpoint:", error); // if (!res.headersSent) { // res.status(500).json({ error: error.message }); // } // } // }); // SSE endpoint (for MCP Inspector) app.get("/sse", async (req, res) => { console.log("SSE connection established (for MCP Inspector)"); try { // Create a new MCP server instance for this connection const server = createMCPServer(); // Generate unique session ID const sessionId = Math.random().toString(36).substring(7); console.log(`Generated session ID: ${sessionId}`); // Create SSE transport with session-specific message endpoint const transport = new SSEServerTransport( `/message?sessionId=${sessionId}`, res ); // Store session before connecting sessions.set(sessionId, { server, transport }); console.log( `Session ${sessionId} stored. Total sessions: ${sessions.size}` ); // Connect server to transport (this will set up SSE headers and send endpoint event) await server.connect(transport); console.log(`Session ${sessionId} connected and ready`); // Handle client disconnect req.on("close", () => { console.log(`SSE connection closed for session ${sessionId}`); sessions.delete(sessionId); console.log( `Session ${sessionId} removed. Remaining sessions: ${sessions.size}` ); server.close().catch(console.error); }); } catch (error) { console.error("Error in SSE endpoint:", error); if (!res.headersSent) { res.status(500).json({ error: error.message }); } } }); // POST endpoint for messages (used by SSE transport) app.post("/message", async (req, res) => { const sessionId = req.query.sessionId; console.log(`Received message for session: ${sessionId}`); console.log(`Available sessions: ${Array.from(sessions.keys()).join(", ")}`); // Validate session ID if (!sessionId) { console.error("Missing sessionId in request"); return res.status(400).json({ error: "Missing sessionId parameter" }); } const session = sessions.get(sessionId); if (!session) { console.error(`Session not found: ${sessionId}`); console.log(`Current sessions: ${Array.from(sessions.keys()).join(", ")}`); return res.status(404).json({ error: "Session not found" }); } console.log(`Processing message for session ${sessionId}`); // Let the transport handle the message try { await session.transport.handlePostMessage(req.body, res); console.log(`Message processed successfully for session ${sessionId}`); } catch (error) { console.error(`Error handling message for session ${sessionId}:`, error); if (!res.headersSent) { res.status(500).json({ error: error.message }); } } }); // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "ok", server: "mcp-demo", version: "1.0.0", transports: ["http-streaming", "sse"], endpoints: { mcp: "/mcp", sse: "/sse", message: "/message", health: "/health", }, }); }); // Root endpoint with information app.get("/", (req, res) => { res.json({ name: "MCP Demo Server", description: "MCP Server with HTTP Streaming and SSE support", version: "1.0.0", transports: ["http-streaming", "sse"], endpoints: { mcp: { path: "/mcp", method: "POST", transport: "http-streaming", description: "HTTP streaming endpoint for stateless MCP communication", }, sse: { path: "/sse", method: "GET", transport: "sse", description: "SSE endpoint for MCP Inspector", }, message: { path: "/message", method: "POST", transport: "sse", description: "Message endpoint for SSE transport", }, health: { path: "/health", method: "GET", description: "Health check endpoint", }, }, tools: [ { name: "read_widget_resource", description: "Reads the content of widgetResource.md file", }, { name: "read_page_resource", description: "Reads the content of pageResource.md file", }, ], }); }); // Error handling middleware app.use((err, req, res, next) => { console.error("Error:", err); res.status(500).json({ error: "Internal Server Error", message: err.message, }); }); // Start server app.listen(PORT, () => { console.log(`🚀 MCP Demo Server running on http://localhost:${PORT}`); console.log(`\n📡 Available Transports:`); console.log(` 1. HTTP Streaming (Stateless):`); console.log(` - MCP endpoint: http://localhost:${PORT}/mcp`); console.log(`\n 2. SSE (for MCP Inspector):`); console.log(` - SSE endpoint: http://localhost:${PORT}/sse`); console.log(` - Message endpoint: http://localhost:${PORT}/message`); console.log(`\n❤️ Health check: http://localhost:${PORT}/health`); console.log(`\n🔧 Available tools:`); console.log(` - read_widget_resource: Reads widgetResource.md`); console.log(` - read_page_resource: Reads pageResource.md`); console.log(`\n💡 Quick Test:`); console.log(` HTTP Streaming: npm run test-streaming`); console.log(` MCP Inspector: npm run test`); }); export default app;

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/jiawang1/mcp-demo'

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