From 88c212c05e597ff933456361ac285acb5770a1e1 Mon Sep 17 00:00:00 2001 From: "nasir@endelospay.com" Date: Tue, 22 Jul 2025 05:18:21 +0500 Subject: [PATCH] fix --- .env | 7 + .env.example | 7 + README.md | 112 ++++++- http-tools-server.js | 704 ++++++++++++++++++++++++++++++++++++++----- index.js | 21 ++ package.json | 16 +- 6 files changed, 779 insertions(+), 88 deletions(-) create mode 100644 index.js diff --git a/.env b/.env index eb03e45..f100bd0 100644 --- a/.env +++ b/.env @@ -10,6 +10,13 @@ MCP_SERVER_VERSION=1.0.0 MCP_SERVER_PORT=3000 MCP_SERVER_HOST=localhost + + +# MCP Transport Configuration +# Options: "stdio" (for Claude Desktop/MCP clients) or "http" (for web clients) +# stdio: Uses stdin/stdout for communication (default for MCP clients) +# http: Uses HTTP server with JSON-RPC 2.0 over HTTP POST +MCP_TRANSPORT=both # Authentication Configuration - Admin ADMIN_USERNAME=admin@healthcare.com ADMIN_PASSWORD=your_admin_password diff --git a/.env.example b/.env.example index eaed990..96793a7 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,13 @@ MCP_SERVER_VERSION=1.0.0 MCP_SERVER_PORT=3000 MCP_SERVER_HOST=localhost +# MCP Transport Configuration +# Options: "stdio", "http", or "both" +# stdio: Uses stdin/stdout for communication (for MCP clients like Claude Desktop) +# http: Uses HTTP server with JSON-RPC 2.0 over HTTP POST (for web clients) +# both: Runs both stdio and HTTP transports simultaneously (recommended) +MCP_TRANSPORT=both + # Authentication Configuration - Admin ADMIN_USERNAME=admin@healthcare.com ADMIN_PASSWORD=your_admin_password diff --git a/README.md b/README.md index 0d0e437..c40da33 100644 --- a/README.md +++ b/README.md @@ -107,24 +107,62 @@ ENABLE_DETAILED_ERRORS=false ### Starting the Server +The server supports three transport modes: + +**1. Both Modes (Recommended - stdio + HTTP simultaneously):** + ```bash -# MCP Server only (stdio protocol) +# Default mode - both transports running simultaneously npm start -# HTTP Server (full features with MCP tools) -npm run start - - -# Both MCP and HTTP servers simultaneously -npm run start +# Explicitly set both modes +npm run start:both +# Development mode with auto-restart +npm run dev:both ``` -### Server URLs +**2. Stdio Mode Only (for MCP clients like Claude Desktop):** -When running the HTTP server, you can access: +```bash +# Stdio transport only +npm run start:stdio + +# Development mode with auto-restart +npm run dev:stdio +``` + +**3. HTTP Mode Only (for web-based clients):** + +```bash +# HTTP transport only +npm run start:http + +# Development mode with auto-restart +npm run dev:http +``` + +### Transport Configuration + +Set the transport mode in your `.env` file: + +```bash +# For both MCP clients and HTTP/web clients (recommended) +MCP_TRANSPORT=both + +# For MCP clients only (Claude Desktop, VS Code MCP extensions) +MCP_TRANSPORT=stdio + +# For HTTP/web clients only +MCP_TRANSPORT=http +``` + +### Server URLs (HTTP Mode) + +When running in HTTP mode, you can access: - **Health Check**: `http://localhost:3000/health` +- **MCP Protocol**: `POST http://localhost:3000/mcp` (JSON-RPC 2.0) - **Tools List**: `http://localhost:3000/tools` - **Server Stats**: `http://localhost:3000/stats` - **Tool Execution**: `POST http://localhost:3000/tools/{toolName}/execute` @@ -132,6 +170,62 @@ When running the HTTP server, you can access: ### Using with MCP Clients +**Claude Desktop Configuration:** + +Add to your Claude Desktop configuration file: + +```json +{ + "mcpServers": { + "laravel-healthcare": { + "command": "node", + "args": ["/path/to/laravel-healthcare-mcp-server/index.js"], + "env": { + "LARAVEL_API_BASE_URL": "https://your-api.com", + "MCP_TRANSPORT": "stdio" + } + } + } +} +``` + +**VS Code MCP Extension:** + +Configure the MCP extension to use: + +- **Command**: `node` +- **Args**: `["/path/to/laravel-healthcare-mcp-server/index.js"]` +- **Environment**: Set `MCP_TRANSPORT=stdio` + +### MCP Protocol Testing + +**Test tool listing:** + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' +``` + +**Test tool execution:** + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "public_manage_login", + "arguments": { + "email": "test@example.com", + "password": "password" + } + }, + "id": 2 + }' +``` + The server implements the Model Context Protocol and can be used with any MCP-compatible client: ```json diff --git a/http-tools-server.js b/http-tools-server.js index c83cdcf..1d3385f 100644 --- a/http-tools-server.js +++ b/http-tools-server.js @@ -1,12 +1,23 @@ #!/usr/bin/env node /** - * HTTP server with MCP tool execution support + * @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(); @@ -17,59 +28,452 @@ console.log( ); 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"; -console.log("๐Ÿš€ Starting Laravel Healthcare MCP HTTP Server with Tools..."); +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 -app.use(cors()); +// 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" })); -// Initialize MCP components +// 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"); - const config = new ConfigManager(); - authManager = new AuthManager(null, config.getAll(true)); - const apiClient = new ApiClient(config.getAll(), authManager); + // 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); - console.log("โœ… MCP components initialized"); + // 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; } } -// Health endpoint +/** + * 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: toolGenerator !== null, + 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 { @@ -361,66 +765,228 @@ app.use((error, req, res, next) => { }); }); -// Initialize and start server -async function startServer() { - const mcpReady = await initializeMCP(); +/** + * Start MCP server in stdio mode (for desktop clients like Claude Desktop) + */ +async function startStdioMode() { + try { + console.log("๐Ÿ”Œ Starting MCP server in stdio mode..."); - if (!mcpReady) { - console.error("โŒ Cannot start server without MCP initialization"); + 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); } - - const server = app.listen(port, host, () => { - const serverUrl = `http://${ - host === "0.0.0.0" ? "localhost" : host - }:${port}`; - - console.log("\n" + "=".repeat(60)); - console.log("๐Ÿš€ LARAVEL HEALTHCARE MCP SERVER - HTTP MODE"); - console.log("=".repeat(60)); - 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("=".repeat(60)); - console.log("๐Ÿ“‹ Available Endpoints:"); - console.log(` โ€ข Health Check: ${serverUrl}/health`); - console.log(` โ€ข Tools List: ${serverUrl}/tools`); - console.log(` โ€ข Server Stats: ${serverUrl}/stats`); - console.log( - ` โ€ข Tool Execute: POST ${serverUrl}/tools/{toolName}/execute` - ); - console.log("=".repeat(60)); - console.log("๐Ÿ“Š Server Status: READY"); - console.log(`โฐ Started at: ${new Date().toLocaleString()}`); - console.log("=".repeat(60)); - console.log("๐Ÿ’ก Press Ctrl+C to stop the server"); - console.log(""); - console.log("๐Ÿงช Test login tool:"); - console.log( - ` curl -X POST ${serverUrl}/tools/public_manage_login/execute \\` - ); - console.log(` -H "Content-Type: application/json" \\`); - console.log( - ` -d '{"email": "test@example.com", "password": "password"}'` - ); - 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")); } -startServer().catch((error) => { - console.error("โŒ Failed to start 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(); diff --git a/index.js b/index.js new file mode 100644 index 0000000..56eb529 --- /dev/null +++ b/index.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +/** + * @fileoverview Main entry point for Laravel Healthcare MCP Server + * Automatically detects transport mode and starts appropriate server + * Default: stdio mode for MCP clients like Claude Desktop + */ + +import dotenv from "dotenv"; + +// Load environment variables +dotenv.config(); + +// Set default transport to stdio for this entry point +process.env.MCP_TRANSPORT = "stdio"; + +// Import and start the server +import("./http-tools-server.js").catch((error) => { + console.error("โŒ Failed to start MCP server:", error); + process.exit(1); +}); diff --git a/package.json b/package.json index 78d0471..3f6f3fd 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,13 @@ "main": "index.js", "scripts": { "start": "node http-tools-server.js", + "start:stdio": "node index.js", + "start:http": "node http-tools-server.js", + "start:both": "node http-tools-server.js", + "dev": "node --watch http-tools-server.js", + "dev:stdio": "node --watch index.js", "dev:http": "node --watch http-tools-server.js", - "test": "node run-tests-simple.js all", - "test:quick": "node run-tests-simple.js quick", - "test:coverage": "node run-tests-simple.js coverage", - "test:watch": "node run-tests-simple.js all --watch", - "test:public": "node run-tests-simple.js suite public", - "test:provider": "node run-tests-simple.js suite provider", - "test:patient": "node run-tests-simple.js suite patient", - "test:business": "node run-tests-simple.js suite business", - "test:healthcare": "node run-tests-simple.js suite healthcare", - "test:errors": "node run-tests-simple.js suite errors" + "dev:both": "node --watch http-tools-server.js" }, "keywords": [ "mcp",