#!/usr/bin/env node /** * @fileoverview MCP-compliant HTTP and stdio server for Laravel Healthcare API * Implements full MCP protocol specification with dual transport support * Based on @modelcontextprotocol/sdk v1.0.4 */ import dotenv from "dotenv"; import express from "express"; import cors from "cors"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, PingRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; // Load environment variables from .env file dotenv.config(); console.log("๐Ÿ”ง Environment variables loaded:"); console.log( ` LARAVEL_API_BASE_URL: ${process.env.LARAVEL_API_BASE_URL || "NOT SET"}` ); console.log(` MCP_SERVER_PORT: ${process.env.MCP_SERVER_PORT || "NOT SET"}`); console.log(` MCP_SERVER_HOST: ${process.env.MCP_SERVER_HOST || "NOT SET"}`); console.log(` MCP_TRANSPORT: ${process.env.MCP_TRANSPORT || "http"}`); console.log(""); // Set default values if not provided process.env.LARAVEL_API_BASE_URL = process.env.LARAVEL_API_BASE_URL || "https://example.com"; const transportMode = process.env.MCP_TRANSPORT || "both"; console.log( `๐Ÿš€ Starting Laravel Healthcare MCP Server (${transportMode} mode)...` ); // HTTP server setup (for HTTP transport mode) const app = express(); const port = process.env.MCP_SERVER_PORT || 3000; const host = process.env.MCP_SERVER_HOST || "0.0.0.0"; // Middleware for HTTP mode app.use( cors({ origin: "*", methods: ["GET", "POST", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"], }) ); app.use(express.json({ limit: "10mb" })); // MCP Server and components let mcpServer = null; let toolGenerator = null; let authManager = null; let configManager = null; /** * Initialize MCP components and create MCP server instance * @returns {Promise} Success status */ async function initializeMCP() { try { console.log("๐Ÿ“‹ Initializing MCP components..."); // Import required modules const { ConfigManager } = await import("./src/config/ConfigManager.js"); const { AuthManager } = await import("./src/auth/AuthManager.js"); const { ApiClient } = await import("./src/proxy/ApiClient.js"); const { ToolGenerator } = await import("./src/tools/ToolGenerator.js"); const { logger } = await import("./src/utils/logger.js"); // Initialize components configManager = new ConfigManager(); const config = configManager.getAll(true); authManager = new AuthManager(null, config); const apiClient = new ApiClient(config, authManager); toolGenerator = new ToolGenerator(apiClient); // Create MCP server instance mcpServer = new Server( { name: config.MCP_SERVER_NAME || "laravel-healthcare-mcp-server", version: config.MCP_SERVER_VERSION || "1.0.0", }, { capabilities: { tools: {}, logging: {}, }, } ); // Setup MCP request handlers await setupMcpHandlers(); console.log("โœ… MCP components and server initialized"); logger.info( `MCP Server initialized with ${ toolGenerator.generateAllTools().length } tools` ); return true; } catch (error) { console.error("โŒ Failed to initialize MCP:", error.message); console.error(error.stack); return false; } } /** * Setup MCP protocol request handlers * Implements all required MCP methods according to specification */ async function setupMcpHandlers() { // Handle initialization requests mcpServer.setRequestHandler(InitializeRequestSchema, async (request) => { const { clientInfo } = request.params; console.log( `๐Ÿค MCP client connected: ${clientInfo?.name || "Unknown"} v${ clientInfo?.version || "Unknown" }` ); return { protocolVersion: "2024-11-05", capabilities: { tools: { listChanged: true, }, logging: {}, }, serverInfo: { name: "laravel-healthcare-mcp-server", version: "1.0.0", }, }; }); // Handle ping requests mcpServer.setRequestHandler(PingRequestSchema, async () => { return {}; }); // Handle list tools requests mcpServer.setRequestHandler(ListToolsRequestSchema, async () => { try { const allTools = toolGenerator.generateAllTools(); const tools = allTools.map((tool) => { const toolDef = toolGenerator.getTool(tool.name); return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema, // Add metadata for better client understanding metadata: { authType: toolDef?.authType || "public", category: getToolCategory(tool.name), endpoint: { method: toolDef?.endpoint?.method, path: toolDef?.endpoint?.path, }, }, }; }); console.log(`๐Ÿ“‹ Listed ${tools.length} tools for MCP client`); return { tools }; } catch (error) { console.error("Error listing tools:", error); throw new Error(`Failed to list tools: ${error.message}`); } }); // Handle call tool requests mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => { const { name: toolName, arguments: toolArgs } = request.params; const startTime = Date.now(); try { console.log(`๐Ÿ”ง MCP tool execution: ${toolName}`); console.log(`๐Ÿ“ Parameters:`, JSON.stringify(toolArgs, null, 2)); // Get tool implementation const tool = toolGenerator.getTool(toolName); if (!tool) { throw new Error(`Tool not found: ${toolName}`); } // Execute tool const result = await tool.execute(toolArgs || {}); // Handle login token extraction for provider authentication if (toolName === "public_manage_login" && result && authManager) { await handleLoginTokenExtraction(result); } const duration = Date.now() - startTime; console.log(`โœ… Tool ${toolName} executed successfully (${duration}ms)`); // Return MCP-compliant response return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { const duration = Date.now() - startTime; console.error( `โŒ Tool execution failed for ${toolName} (${duration}ms):`, error.message ); // Return MCP-compliant error response return { content: [ { type: "text", text: JSON.stringify( { error: true, message: error.message, toolName: toolName, timestamp: new Date().toISOString(), }, null, 2 ), }, ], isError: true, }; } }); } /** * Extract and categorize tool by name pattern * @param {string} toolName - Tool name * @returns {string} Category */ function getToolCategory(toolName) { if (toolName.startsWith("public_")) return "authentication"; if (toolName.startsWith("provider_")) return "clinical"; if (toolName.startsWith("patient_")) return "patient-portal"; if (toolName.startsWith("partner_")) return "business"; if (toolName.startsWith("affiliate_")) return "affiliate"; if (toolName.startsWith("network_")) return "network"; return "general"; } /** * Handle token extraction from login responses * @param {Object} result - Login response result */ async function handleLoginTokenExtraction(result) { try { let token = null; let expiresIn = 3600; // Default 1 hour let userData = null; // Extract token from different possible response formats if (result.accessToken || result.access_token || result.token) { token = result.accessToken || result.access_token || result.token; expiresIn = result.expiresIn || result.expires_in || 3600; userData = result.userData || result.user || result.data || null; } else if (result.data) { // Token might be nested in data object token = result.data.accessToken || result.data.access_token || result.data.token; expiresIn = result.data.expiresIn || result.data.expires_in || 3600; userData = result.data.userData || result.data.user || null; } if (token) { // Store token for provider auth type authManager.setToken("provider", token, expiresIn, userData); console.log( `๐Ÿ”‘ Stored bearer token for provider authentication (expires in ${expiresIn}s)` ); // Add token info to response result._tokenInfo = { stored: true, authType: "provider", expiresIn: expiresIn, message: "Token automatically stored for provider endpoints", }; } else { console.log(`โš ๏ธ No token found in login response`); } } catch (error) { console.error( `โŒ Failed to store token from login response:`, error.message ); } } // Health endpoint for HTTP mode app.get("/health", (req, res) => { res.json({ status: "healthy", timestamp: new Date().toISOString(), server: "Laravel Healthcare MCP Server", version: "1.0.0", transport: transportMode, port: port, mcpInitialized: mcpServer !== null && toolGenerator !== null, protocolVersion: "2024-11-05", capabilities: { tools: true, logging: true, }, }); }); // MCP protocol endpoint for HTTP transport app.post("/mcp", async (req, res) => { try { if (!mcpServer) { return res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "MCP server not initialized", }, id: req.body.id || null, }); } // Handle JSON-RPC 2.0 requests const request = req.body; // Validate JSON-RPC format if (!request.jsonrpc || request.jsonrpc !== "2.0") { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request - missing or invalid jsonrpc field", }, id: request.id || null, }); } if (!request.method) { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request - missing method field", }, id: request.id || null, }); } console.log(`๐Ÿ“จ MCP HTTP request: ${request.method}`); // Route to appropriate handler based on method let result; try { switch (request.method) { case "initialize": // Handle initialization directly result = { protocolVersion: "2024-11-05", capabilities: { tools: { listChanged: true, }, logging: {}, }, serverInfo: { name: "laravel-healthcare-mcp-server", version: "1.0.0", }, }; break; case "tools/list": // Handle tools list const allTools = toolGenerator.generateAllTools(); const tools = allTools.map((tool) => { const toolDef = toolGenerator.getTool(tool.name); return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema, }; }); result = { tools }; break; case "tools/call": // Handle tool execution const { name: toolName, arguments: toolArgs } = request.params; const tool = toolGenerator.getTool(toolName); if (!tool) { throw new Error(`Tool not found: ${toolName}`); } const toolResult = await tool.execute(toolArgs || {}); // Handle login token extraction if (toolName === "public_manage_login" && toolResult && authManager) { await handleLoginTokenExtraction(toolResult); } result = { content: [ { type: "text", text: JSON.stringify(toolResult, null, 2), }, ], }; break; case "ping": result = {}; break; default: return res.status(400).json({ jsonrpc: "2.0", error: { code: -32601, message: `Method not found: ${request.method}`, }, id: request.id || null, }); } // Return successful JSON-RPC response res.json({ jsonrpc: "2.0", result: result, id: request.id || null, }); } catch (error) { console.error(`โŒ MCP request error:`, error); // Return JSON-RPC error response res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: error.message || "Internal error", data: { method: request.method, timestamp: new Date().toISOString(), }, }, id: request.id || null, }); } } catch (error) { console.error("โŒ MCP endpoint error:", error); res.status(500).json({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error", }, id: null, }); } }); // List all tools app.get("/tools", (req, res) => { try { if (!toolGenerator) { return res.status(500).json({ error: "MCP not initialized" }); } const tools = toolGenerator.generateAllTools(); const toolsWithDetails = tools.map((tool) => { const toolDef = toolGenerator.getTool(tool.name); return { name: tool.name, description: tool.description, authType: toolDef?.authType, method: toolDef?.endpoint?.method, path: toolDef?.endpoint?.path, inputSchema: tool.inputSchema, }; }); res.json({ total: toolsWithDetails.length, tools: toolsWithDetails, }); } catch (error) { console.error("Failed to list tools:", error); res.status(500).json({ error: error.message }); } }); // Get specific tool app.get("/tools/:toolName", (req, res) => { try { if (!toolGenerator) { return res.status(500).json({ error: "MCP not initialized" }); } const { toolName } = req.params; const tool = toolGenerator.getTool(toolName); if (!tool) { return res.status(404).json({ error: "Tool not found" }); } res.json({ name: tool.name, description: tool.description, authType: tool.authType, endpoint: tool.endpoint, inputSchema: tool.inputSchema, }); } catch (error) { console.error("Failed to get tool:", error); res.status(500).json({ error: error.message }); } }); // Execute MCP tool app.post("/tools/:toolName/execute", async (req, res) => { const { toolName } = req.params; const parameters = req.body; try { if (!toolGenerator) { return res.status(500).json({ error: "MCP not initialized" }); } console.log(`๐Ÿ”ง Executing tool: ${toolName}`); console.log(`๐Ÿ“ Parameters:`, JSON.stringify(parameters, null, 2)); // Get all tools and find the one we want const allTools = toolGenerator.generateAllTools(); const toolDef = allTools.find((tool) => tool.name === toolName); if (!toolDef) { console.log(`โŒ Tool ${toolName} not found in generated tools`); return res.status(404).json({ error: "Tool not found" }); } console.log(`๐Ÿ” Found tool: ${toolDef.name}`); // Get the actual tool implementation const tool = toolGenerator.getTool(toolName); console.log(tool); if (!tool || !tool.execute) { console.log(`โŒ Tool ${toolName} has no execute method`); return res .status(500) .json({ error: "Tool execution method not available" }); } console.log(`๐Ÿš€ Executing tool...`); const result = await tool.execute(parameters); console.log(`โœ… Tool ${toolName} executed successfully`); console.log(`๐Ÿ“Š Result:`, JSON.stringify(result, null, 2)); // Special handling for login tools - extract and store token if (toolName === "public_manage_login" && result && authManager) { try { let token = null; let expiresIn = 3600; // Default 1 hour let userData = null; // Extract token from different possible response formats if (result.accessToken || result.access_token || result.token) { token = result.accessToken || result.access_token || result.token; expiresIn = result.expiresIn || result.expires_in || 3600; userData = result.userData || result.user || result.data || null; } else if (result.data) { // Token might be nested in data object token = result.data.accessToken || result.data.access_token || result.data.token; expiresIn = result.data.expiresIn || result.data.expires_in || 3600; userData = result.data.userData || result.data.user || null; } if (token) { // Store token for provider auth type authManager.setToken("provider", token, expiresIn, userData); console.log( `๐Ÿ”‘ Stored bearer token for provider authentication (expires in ${expiresIn}s)` ); // Add token info to response result._tokenInfo = { stored: true, authType: "provider", expiresIn: expiresIn, message: "Token automatically stored for provider endpoints", }; } else { console.log(`โš ๏ธ No token found in login response`); } } catch (error) { console.error( `โŒ Failed to store token from login response:`, error.message ); } } res.json({ success: true, toolName, result, }); } catch (error) { console.error(`โŒ Tool execution failed for ${toolName}:`, error.message); console.error(`๐Ÿ“‹ Error stack:`, error.stack); res.status(error.status || 500).json({ success: false, toolName, error: error.message, details: error.details || null, stack: error.stack, }); } }); // Server stats app.get("/stats", (req, res) => { try { if (!toolGenerator) { return res.status(500).json({ error: "MCP not initialized" }); } const tools = toolGenerator.generateAllTools(); const publicTools = tools.filter((tool) => { const toolDef = toolGenerator.getTool(tool.name); return toolDef?.authType === "public"; }); const providerTools = tools.filter((tool) => { const toolDef = toolGenerator.getTool(tool.name); return toolDef?.authType === "provider"; }); res.json({ server: { name: "Laravel Healthcare MCP Server", version: "1.0.0", uptime: process.uptime(), memory: process.memoryUsage(), }, tools: { total: tools.length, public: publicTools.length, provider: providerTools.length, }, config: { port: port, host: host, apiUrl: process.env.LARAVEL_API_BASE_URL, }, authentication: { hasProviderToken: authManager ? authManager.getCacheStats().keys?.includes("token_provider") || false : false, }, }); } catch (error) { console.error("Failed to get stats:", error); res.status(500).json({ error: error.message }); } }); // Set bearer token manually app.post("/auth/set-token", (req, res) => { try { if (!authManager) { return res.status(500).json({ error: "Auth manager not initialized" }); } const { authType = "provider", token, expiresIn = 3600, userData = null, } = req.body; if (!token) { return res.status(400).json({ error: "Token is required" }); } authManager.setToken(authType, token, expiresIn, userData); res.json({ success: true, message: `Token set for ${authType}`, authType, expiresIn, }); } catch (error) { console.error("Failed to set token:", error); res.status(500).json({ error: error.message }); } }); // Get auth status app.get("/auth/status", (req, res) => { try { if (!authManager) { return res.status(500).json({ error: "Auth manager not initialized" }); } const cacheStats = authManager.getCacheStats(); const hasProviderToken = cacheStats.keys?.includes("token_provider") || false; res.json({ authManager: "initialized", cacheStats, tokens: { provider: hasProviderToken ? "present" : "missing", }, }); } catch (error) { console.error("Failed to get auth status:", error); res.status(500).json({ error: error.message }); } }); // 404 handler app.use("*", (req, res) => { res.status(404).json({ error: "Endpoint not found", availableEndpoints: [ "GET /health", "GET /tools", "GET /tools/:toolName", "POST /tools/:toolName/execute", "GET /stats", "POST /auth/set-token", "GET /auth/status", ], }); }); // Error handler app.use((error, req, res, next) => { console.error("HTTP server error:", error); res.status(500).json({ error: "Internal server error", message: error.message, }); }); /** * Start MCP server in stdio mode (for desktop clients like Claude Desktop) */ async function startStdioMode() { try { console.log("๐Ÿ”Œ Starting MCP server in stdio mode..."); const mcpReady = await initializeMCP(); if (!mcpReady) { console.error("โŒ Cannot start server without MCP initialization"); process.exit(1); } // Create stdio transport const transport = new StdioServerTransport(); // Connect server to transport await mcpServer.connect(transport); console.log("โœ… MCP Server started in stdio mode"); console.log( `๐Ÿ“Š Available tools: ${toolGenerator.generateAllTools().length}` ); console.log("๐Ÿ”— Connected to stdio transport"); console.log("๐Ÿ’ก Server ready for MCP client connections"); } catch (error) { console.error("โŒ Failed to start stdio server:", error); process.exit(1); } } /** * Start MCP server in HTTP mode (for web-based clients) */ async function startHttpMode() { try { console.log("๐ŸŒ Starting MCP server in HTTP mode..."); const mcpReady = await initializeMCP(); if (!mcpReady) { console.error("โŒ Cannot start server without MCP initialization"); process.exit(1); } const server = app.listen(port, host, () => { const serverUrl = `http://${ host === "0.0.0.0" ? "localhost" : host }:${port}`; const toolCount = toolGenerator.generateAllTools().length; console.log("\n" + "=".repeat(70)); console.log("๐Ÿš€ LARAVEL HEALTHCARE MCP SERVER - HTTP MODE"); console.log("=".repeat(70)); console.log(`๐Ÿ“ก Server URL: ${serverUrl}`); console.log(`๐ŸŒ Host: ${host}`); console.log(`๐Ÿ”Œ Port: ${port}`); console.log(`๐Ÿ”— API URL: ${process.env.LARAVEL_API_BASE_URL}`); console.log(`๐Ÿ“Š Available Tools: ${toolCount}`); console.log(`๐Ÿ”„ Protocol Version: 2024-11-05`); console.log("=".repeat(70)); console.log("๐Ÿ“‹ MCP Endpoints:"); console.log(` โ€ข Health Check: ${serverUrl}/health`); console.log(` โ€ข MCP Protocol: POST ${serverUrl}/mcp`); console.log(` โ€ข Tools List: ${serverUrl}/tools`); console.log(` โ€ข Server Stats: ${serverUrl}/stats`); console.log("=".repeat(70)); console.log("๐Ÿงช Test MCP Protocol:"); console.log(` curl -X POST ${serverUrl}/mcp \\`); console.log(` -H "Content-Type: application/json" \\`); console.log(` -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'`); console.log(""); console.log("๐Ÿ”‘ Test Login Tool:"); console.log(` curl -X POST ${serverUrl}/mcp \\`); console.log(` -H "Content-Type: application/json" \\`); console.log( ` -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"public_manage_login","arguments":{"email":"test@example.com","password":"password"}},"id":2}'` ); console.log("=".repeat(70)); console.log("๐Ÿ“Š Server Status: READY"); console.log(`โฐ Started at: ${new Date().toLocaleString()}`); console.log("=".repeat(70)); console.log("๐Ÿ’ก Press Ctrl+C to stop the server"); console.log(""); }); // Graceful shutdown const shutdown = (signal) => { console.log(`\n๐Ÿ›‘ Received ${signal}, shutting down HTTP server...`); server.close(() => { console.log("โœ… HTTP server stopped"); process.exit(0); }); }; process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGTERM", () => shutdown("SIGTERM")); } catch (error) { console.error("โŒ Failed to start HTTP server:", error); process.exit(1); } } /** * Start MCP server in both stdio and HTTP modes simultaneously */ async function startBothModes() { try { console.log("๐Ÿ”„ Starting MCP server in dual transport mode (stdio + HTTP)..."); const mcpReady = await initializeMCP(); if (!mcpReady) { console.error("โŒ Cannot start server without MCP initialization"); process.exit(1); } // Start stdio transport in background const stdioPromise = (async () => { try { const transport = new StdioServerTransport(); await mcpServer.connect(transport); console.log("โœ… Stdio transport connected and ready"); } catch (error) { console.error("โŒ Failed to start stdio transport:", error); // Don't exit - HTTP mode can still work } })(); // Start HTTP server const httpPromise = new Promise((resolve, reject) => { const server = app.listen(port, host, () => { const serverUrl = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`; const toolCount = toolGenerator.generateAllTools().length; console.log("\n" + "=".repeat(70)); console.log("๐Ÿš€ LARAVEL HEALTHCARE MCP SERVER - DUAL MODE"); console.log("=".repeat(70)); console.log("๐Ÿ“ก Transports:"); console.log(` โ€ข Stdio: Ready for MCP clients (Claude Desktop, VS Code)`); console.log(` โ€ข HTTP: ${serverUrl}`); console.log("=".repeat(70)); console.log(`๐ŸŒ Host: ${host}`); console.log(`๐Ÿ”Œ Port: ${port}`); console.log(`๐Ÿ”— API URL: ${process.env.LARAVEL_API_BASE_URL}`); console.log(`๐Ÿ“Š Available Tools: ${toolCount}`); console.log(`๐Ÿ”„ Protocol Version: 2024-11-05`); console.log("=".repeat(70)); console.log("๐Ÿ“‹ HTTP Endpoints:"); console.log(` โ€ข Health Check: ${serverUrl}/health`); console.log(` โ€ข MCP Protocol: POST ${serverUrl}/mcp`); console.log(` โ€ข Tools List: ${serverUrl}/tools`); console.log(` โ€ข Server Stats: ${serverUrl}/stats`); console.log("=".repeat(70)); console.log("๐Ÿงช Test MCP Protocol (HTTP):"); console.log(` curl -X POST ${serverUrl}/mcp \\`); console.log(` -H "Content-Type: application/json" \\`); console.log(` -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'`); console.log(""); console.log("๐Ÿ”‘ Test Login Tool (HTTP):"); console.log(` curl -X POST ${serverUrl}/mcp \\`); console.log(` -H "Content-Type: application/json" \\`); console.log( ` -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"public_manage_login","arguments":{"email":"test@example.com","password":"password"}},"id":2}'` ); console.log("=".repeat(70)); console.log("๐Ÿ“Š Server Status: READY (Both Transports)"); console.log(`โฐ Started at: ${new Date().toLocaleString()}`); console.log("=".repeat(70)); console.log("๐Ÿ’ก Press Ctrl+C to stop the server"); console.log(""); resolve(server); }); server.on('error', reject); }); // Wait for both transports to initialize const [, server] = await Promise.all([stdioPromise, httpPromise]); // Graceful shutdown for both transports const shutdown = (signal) => { console.log(`\n๐Ÿ›‘ Received ${signal}, shutting down both transports...`); server.close(() => { console.log("โœ… HTTP server stopped"); console.log("โœ… Stdio transport stopped"); process.exit(0); }); }; process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGTERM", () => shutdown("SIGTERM")); } catch (error) { console.error("โŒ Failed to start dual transport server:", error); process.exit(1); } } /** * Main entry point - start server based on transport mode */ async function main() { try { console.log(`๐ŸŽฏ Transport mode: ${transportMode}`); if (transportMode === "stdio") { await startStdioMode(); } else if (transportMode === "http") { await startHttpMode(); } else if (transportMode === "both") { await startBothModes(); } else { console.error(`โŒ Invalid transport mode: ${transportMode}`); console.log("๐Ÿ’ก Valid modes: 'stdio', 'http', or 'both'"); console.log("๐Ÿ’ก Set MCP_TRANSPORT environment variable"); process.exit(1); } } catch (error) { console.error("โŒ Failed to start server:", error); console.error(error.stack); process.exit(1); } } // Start the server main();