Skip to main content
Glama
routes.ts13.4 kB
import type { Express, Request, Response, NextFunction } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { insertServerSchema, insertAppSchema, insertActivitySchema, insertToolSchema, type InsertServer, type InsertApp, type InsertActivity, type InsertTool } from "@shared/schema"; import { ZodError } from "zod"; import { fromZodError } from "zod-validation-error"; import { WebSocketServer } from "ws"; import type { WebSocket } from "ws"; import { z } from "zod"; import fetch from "node-fetch"; import { config } from "./config"; import { registryCategories, discoverGitHubServers, discoverSmitheryPackages, searchRegistry, installServer, uninstallServer, startServer, stopServer, getServerStatus, MCPServer } from "./registry-manager"; // Import WebSocket from ws (needed to access OPEN status) import { WebSocket as WSType } from "ws"; /** * Register routes for Express application * @param app Express application * @returns HTTP server */ export async function registerRoutes(app: Express): Promise<Server> { const server = createServer(app); const wss = new WebSocketServer({ server, path: "/ws" }); // Use fixed path for WebSocket // WebSocket connection handler wss.on("connection", handleWebSocketConnection); // API routes app.get("/api/health", (_req, res) => { res.json({ status: "ok" }); }); // Server management API app.get("/api/servers", async (_req, res) => { const servers = await storage.getServers(); res.json(servers); }); app.get("/api/servers/:id", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } const server = await storage.getServer(serverId); if (!server) { return res.status(404).json({ message: "Server not found" }); } res.json(server); }); app.post("/api/servers", async (req, res) => { try { const insertServer = insertServerSchema.parse(req.body); const server = await storage.createServer(insertServer); res.status(201).json(server); } catch (error) { if (error instanceof ZodError) { return res.status(400).json({ message: fromZodError(error).message }); } throw error; } }); app.put("/api/servers/:id", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } try { const updateServer = insertServerSchema.partial().parse(req.body); const server = await storage.updateServer(serverId, updateServer); if (!server) { return res.status(404).json({ message: "Server not found" }); } res.json(server); } catch (error) { if (error instanceof ZodError) { return res.status(400).json({ message: fromZodError(error).message }); } throw error; } }); app.delete("/api/servers/:id", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } const success = await storage.deleteServer(serverId); if (!success) { return res.status(404).json({ message: "Server not found" }); } res.status(204).end(); }); // Tool management API app.get("/api/tools", async (_req, res) => { const tools = await storage.getTools(); res.json(tools); }); app.get("/api/tools/:id", async (req, res) => { const toolId = parseInt(req.params.id, 10); if (isNaN(toolId)) { return res.status(400).json({ message: "Invalid tool ID" }); } const tool = await storage.getTool(toolId); if (!tool) { return res.status(404).json({ message: "Tool not found" }); } res.json(tool); }); // Get tools for a specific server app.get("/api/servers/:id/tools", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } const server = await storage.getServer(serverId); if (!server) { return res.status(404).json({ message: "Server not found" }); } const tools = await storage.getToolsByServerId(serverId); res.json(tools); }); // Registry API endpoints (MCP Dockmaster style) app.get("/api/registry/categories", (_req, res) => { res.json(registryCategories); }); // Search for MCP servers in the registry app.get("/api/registry/search", async (req, res) => { const query = req.query.q as string || ""; const category = req.query.category as string || "all"; if (!registryCategories.includes(category)) { return res.status(400).json({ error: `Invalid category. Valid categories are: ${registryCategories.join(', ')}` }); } try { const results = await searchRegistry(query, category); res.json({ servers: results }); } catch (error) { console.error("Error searching registry:", error); res.status(500).json({ error: "Failed to search registry", message: error instanceof Error ? error.message : String(error) }); } }); // Discover new MCP servers app.post("/api/registry/discover", async (req, res) => { try { // Run discovery process concurrently const [githubServers, smitheryPackages] = await Promise.all([ discoverGitHubServers(), discoverSmitheryPackages() ]); const servers = [...githubServers, ...smitheryPackages]; return res.json({ success: true, discoveredServers: servers.length, servers }); } catch (error) { console.error("Error discovering servers:", error); res.status(500).json({ error: "Failed to discover servers", message: "Error discovering servers", error: error instanceof Error ? error.message : String(error) }); } }); // Install a server from the registry app.post("/api/registry/install/:id", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } try { const server = await installServer(serverId); if (!server) { return res.status(404).json({ message: "Server not found" }); } res.json({ message: "Installation started", server }); } catch (error) { console.error(`Error installing server ${serverId}:`, error); res.status(500).json({ message: "Error installing server", error: error instanceof Error ? error.message : String(error) }); } }); // Uninstall a server app.post("/api/registry/uninstall/:id", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } try { const server = await uninstallServer(serverId); if (!server) { return res.status(404).json({ message: "Server not found" }); } res.json({ message: "Uninstallation started", server }); } catch (error) { console.error(`Error uninstalling server ${serverId}:`, error); res.status(500).json({ message: "Error uninstalling server", error: error instanceof Error ? error.message : String(error) }); } }); // Get all activities app.get("/api/activities", async (req, res) => { const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined; const activities = await storage.getActivities(limit); res.json(activities); }); // Get activities for a specific server app.get("/api/servers/:id/activities", async (req, res) => { const serverId = parseInt(req.params.id, 10); if (isNaN(serverId)) { return res.status(400).json({ message: "Invalid server ID" }); } const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined; const activities = await storage.getActivitiesByServerId(serverId, limit); res.json(activities); }); return server; } /** * Handle WebSocket connection * @param ws WebSocket connection */ function handleWebSocketConnection(ws: WebSocket) { ws.on("message", async (message) => { try { const request = JSON.parse(message.toString()); // Process JSON-RPC request if (request.jsonrpc === "2.0" && request.method) { const response = await handleJsonRpcRequest(request); ws.send(JSON.stringify(response)); } } catch (error) { console.error("Error processing WebSocket message:", error); ws.send(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error", data: error instanceof Error ? error.message : String(error) } })); } }); } /** * Handle JSON-RPC request * @param request JSON-RPC request * @returns JSON-RPC response */ async function handleJsonRpcRequest(request: any) { const { id, method, params } = request; // Default response structure const response = { jsonrpc: "2.0", id }; try { switch (method) { case "ping": return { ...response, result: { pong: true, timestamp: new Date().toISOString() } }; case "getServers": const servers = await storage.getServers(); return { ...response, result: servers }; case "getServer": if (!params || !params.id) { throw new Error("Server ID is required"); } const server = await storage.getServer(params.id); if (!server) { throw new Error("Server not found"); } return { ...response, result: server }; case "toggleWorker": if (!params || !params.id) { throw new Error("Server ID is required"); } const serverToToggle = await storage.getServer(params.id); if (!serverToToggle) { throw new Error("Server not found"); } const updatedServer = await storage.updateServer(params.id, { isWorker: !serverToToggle.isWorker }); return { ...response, result: updatedServer }; case "getTools": const tools = await storage.getTools(); return { ...response, result: tools }; case "getToolsByServer": if (!params || !params.serverId) { throw new Error("Server ID is required"); } const serverTools = await storage.getToolsByServerId(params.serverId); return { ...response, result: serverTools }; case "activateTool": if (!params || !params.id) { throw new Error("Tool ID is required"); } const toolToActivate = await storage.getTool(params.id); if (!toolToActivate) { throw new Error("Tool not found"); } const activatedTool = await storage.updateTool(params.id, { active: true }); return { ...response, result: activatedTool }; case "deactivateTool": if (!params || !params.id) { throw new Error("Tool ID is required"); } const toolToDeactivate = await storage.getTool(params.id); if (!toolToDeactivate) { throw new Error("Tool not found"); } const deactivatedTool = await storage.updateTool(params.id, { active: false }); return { ...response, result: deactivatedTool }; case "getStats": // Get all servers const allServers = await storage.getServers(); // Get active servers count const activeServers = allServers.filter(s => s.status === "active"); // Get all tools const allTools = await storage.getTools(); // Get active tools count const activeTools = allTools.filter(t => t.active); // Get recent activities const recentActivities = await storage.getActivities(10); return { ...response, result: { servers: { total: allServers.length, active: activeServers.length }, tools: { total: allTools.length, active: activeTools.length }, activities: recentActivities } }; // Unknown method default: throw new Error(`Method not found: ${method}`); } } catch (error) { return { ...response, error: { code: -32603, message: error instanceof Error ? error.message : String(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/samihalawa/2025-FINAL-mcpMaster'

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