462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* @fileoverview HTTP Server for Laravel Healthcare MCP Server
|
|
* Provides HTTP endpoints for testing, monitoring, and direct API access
|
|
*/
|
|
|
|
import express from "express";
|
|
import cors from "cors";
|
|
import { ConfigManager } from "./src/config/ConfigManager.js";
|
|
import { AuthManager } from "./src/auth/AuthManager.js";
|
|
import { ApiClient } from "./src/proxy/ApiClient.js";
|
|
import { ToolGenerator } from "./src/tools/ToolGenerator.js";
|
|
import { logger, auditLog } from "./src/utils/logger.js";
|
|
import { ErrorHandler } from "./src/utils/errors.js";
|
|
|
|
/**
|
|
* HTTP Server class for Laravel Healthcare MCP Server
|
|
*/
|
|
class HttpServer {
|
|
constructor() {
|
|
this.app = express();
|
|
this.config = null;
|
|
this.authManager = null;
|
|
this.apiClient = null;
|
|
this.toolGenerator = null;
|
|
this.server = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize the HTTP server
|
|
*/
|
|
async initialize() {
|
|
try {
|
|
console.log("🔄 Initializing HTTP Server...");
|
|
logger.info("Initializing HTTP Server...");
|
|
|
|
// Load configuration
|
|
this.config = new ConfigManager();
|
|
|
|
// Initialize components
|
|
this.authManager = new AuthManager(null, this.config.getAll(true));
|
|
this.apiClient = new ApiClient(this.config.getAll(), this.authManager);
|
|
this.toolGenerator = new ToolGenerator(this.apiClient);
|
|
|
|
// Setup Express middleware
|
|
this.setupMiddleware();
|
|
|
|
// Setup routes
|
|
this.setupRoutes();
|
|
|
|
console.log("✅ HTTP Server initialized successfully");
|
|
logger.info("HTTP Server initialized successfully");
|
|
} catch (error) {
|
|
logger.error("Failed to initialize HTTP server:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup Express middleware
|
|
*/
|
|
setupMiddleware() {
|
|
// CORS
|
|
if (this.config.get("ENABLE_CORS", true)) {
|
|
const corsOrigins = this.config.get("CORS_ORIGINS", "*");
|
|
this.app.use(
|
|
cors({
|
|
origin: corsOrigins === "*" ? true : corsOrigins.split(","),
|
|
credentials: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
// JSON parsing
|
|
this.app.use(express.json({ limit: "10mb" }));
|
|
this.app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
|
|
|
// Request logging
|
|
this.app.use((req, res, next) => {
|
|
logger.debug(`HTTP ${req.method} ${req.path}`, {
|
|
ip: req.ip,
|
|
userAgent: req.get("User-Agent"),
|
|
query: req.query,
|
|
});
|
|
next();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup HTTP routes
|
|
*/
|
|
setupRoutes() {
|
|
// Health check endpoint
|
|
this.app.get("/health", (req, res) => {
|
|
try {
|
|
const health = {
|
|
status: "healthy",
|
|
timestamp: new Date().toISOString(),
|
|
server: {
|
|
name: this.config.get("MCP_SERVER_NAME"),
|
|
version: this.config.get("MCP_SERVER_VERSION"),
|
|
uptime: process.uptime(),
|
|
},
|
|
tools: {
|
|
total: this.toolGenerator.getToolNames().length,
|
|
categories: this.getToolCategories(),
|
|
},
|
|
auth: {
|
|
configured: this.getConfiguredAuthTypes(),
|
|
cacheStats: this.authManager.getCacheStats(),
|
|
},
|
|
api: this.apiClient.getHealthStatus(),
|
|
};
|
|
|
|
res.json(health);
|
|
} catch (error) {
|
|
logger.error("Health check failed:", error);
|
|
res.status(500).json({
|
|
status: "unhealthy",
|
|
error: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
|
|
// List all MCP tools
|
|
this.app.get("/tools", (req, res) => {
|
|
try {
|
|
const tools = this.toolGenerator.generateAllTools();
|
|
const toolsWithDetails = tools.map((tool) => {
|
|
const toolDef = this.toolGenerator.getTool(tool.name);
|
|
return {
|
|
name: tool.name,
|
|
description: tool.description,
|
|
authType: toolDef?.authType,
|
|
category: toolDef?.endpoint?.category,
|
|
method: toolDef?.endpoint?.method,
|
|
path: toolDef?.endpoint?.path,
|
|
inputSchema: tool.inputSchema,
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
total: toolsWithDetails.length,
|
|
tools: toolsWithDetails,
|
|
});
|
|
} catch (error) {
|
|
logger.error("Failed to list tools:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Get tool by name
|
|
this.app.get("/tools/:toolName", (req, res) => {
|
|
try {
|
|
const { toolName } = req.params;
|
|
const tool = this.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) {
|
|
logger.error("Failed to get tool:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Execute MCP tool via HTTP
|
|
this.app.post("/tools/:toolName/execute", async (req, res) => {
|
|
const { toolName } = req.params;
|
|
const parameters = req.body;
|
|
|
|
try {
|
|
logger.info(`HTTP execution of tool: ${toolName}`);
|
|
|
|
const tool = this.toolGenerator.getTool(toolName);
|
|
if (!tool) {
|
|
return res.status(404).json({ error: "Tool not found" });
|
|
}
|
|
|
|
const result = await tool.execute(parameters);
|
|
|
|
auditLog("tool_executed_http", "http_user", {
|
|
toolName,
|
|
authType: tool.authType,
|
|
success: true,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
toolName,
|
|
result,
|
|
});
|
|
} catch (error) {
|
|
logger.error(`HTTP tool execution failed for ${toolName}:`, error);
|
|
|
|
auditLog("tool_executed_http", "http_user", {
|
|
toolName,
|
|
success: false,
|
|
error: error.message,
|
|
});
|
|
|
|
const errorResponse = ErrorHandler.handleMcpError(error, toolName);
|
|
res.status(error.status || 500).json({
|
|
success: false,
|
|
toolName,
|
|
...errorResponse,
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get server statistics
|
|
this.app.get("/stats", (req, res) => {
|
|
try {
|
|
const stats = {
|
|
server: {
|
|
name: this.config.get("MCP_SERVER_NAME"),
|
|
version: this.config.get("MCP_SERVER_VERSION"),
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
nodeVersion: process.version,
|
|
},
|
|
tools: {
|
|
total: this.toolGenerator.getToolNames().length,
|
|
byAuthType: this.getToolsByAuthType(),
|
|
byCategory: this.getToolCategories(),
|
|
},
|
|
auth: {
|
|
configured: this.getConfiguredAuthTypes(),
|
|
cacheStats: this.authManager.getCacheStats(),
|
|
},
|
|
config: this.config.getSummary(),
|
|
};
|
|
|
|
res.json(stats);
|
|
} catch (error) {
|
|
logger.error("Failed to get stats:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Configuration endpoint (non-sensitive)
|
|
this.app.get("/config", (req, res) => {
|
|
try {
|
|
const config = this.config.getAll(false); // Don't include sensitive data
|
|
res.json(config);
|
|
} catch (error) {
|
|
logger.error("Failed to get config:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Authentication status
|
|
this.app.get("/auth/status", async (req, res) => {
|
|
try {
|
|
const results = await this.authManager.validateAllCredentials();
|
|
res.json({
|
|
authTypes: results,
|
|
summary: {
|
|
total: Object.keys(results).length,
|
|
valid: Object.values(results).filter((r) => r.valid).length,
|
|
invalid: Object.values(results).filter((r) => !r.valid).length,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error("Failed to check auth status:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// 404 handler
|
|
this.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",
|
|
"GET /config",
|
|
"GET /auth/status",
|
|
],
|
|
});
|
|
});
|
|
|
|
// Error handler
|
|
this.app.use((error, req, res, next) => {
|
|
logger.error("HTTP server error:", error);
|
|
res.status(500).json({
|
|
error: "Internal server error",
|
|
message: error.message,
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get configured authentication types
|
|
*/
|
|
getConfiguredAuthTypes() {
|
|
const summary = this.config.getSummary();
|
|
return summary.authTypesConfigured;
|
|
}
|
|
|
|
/**
|
|
* Get tool categories summary
|
|
*/
|
|
getToolCategories() {
|
|
const tools = this.toolGenerator.generateAllTools();
|
|
const categories = {};
|
|
|
|
tools.forEach((tool) => {
|
|
const toolDef = this.toolGenerator.getTool(tool.name);
|
|
if (toolDef?.endpoint?.category) {
|
|
const category = toolDef.endpoint.category;
|
|
categories[category] = (categories[category] || 0) + 1;
|
|
}
|
|
});
|
|
|
|
return categories;
|
|
}
|
|
|
|
/**
|
|
* Get tools by auth type summary
|
|
*/
|
|
getToolsByAuthType() {
|
|
const tools = this.toolGenerator.generateAllTools();
|
|
const authTypes = {};
|
|
|
|
tools.forEach((tool) => {
|
|
const toolDef = this.toolGenerator.getTool(tool.name);
|
|
if (toolDef?.authType) {
|
|
const authType = toolDef.authType;
|
|
authTypes[authType] = (authTypes[authType] || 0) + 1;
|
|
}
|
|
});
|
|
|
|
return authTypes;
|
|
}
|
|
|
|
/**
|
|
* Start the HTTP server
|
|
*/
|
|
async start() {
|
|
const port = this.config.get("MCP_SERVER_PORT", 3000);
|
|
const host = this.config.get("MCP_SERVER_HOST", "0.0.0.0");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.server = this.app.listen(port, host, (error) => {
|
|
if (error) {
|
|
logger.error("Failed to start HTTP server:", error);
|
|
reject(error);
|
|
} else {
|
|
const serverUrl = `http://${
|
|
host === "0.0.0.0" ? "localhost" : host
|
|
}:${port}`;
|
|
|
|
logger.info(`HTTP Server started on http://${host}:${port}`);
|
|
|
|
// Clear console output with startup banner
|
|
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("=".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(` • Auth Status: ${serverUrl}/auth/status`);
|
|
console.log(` • Configuration: ${serverUrl}/config`);
|
|
console.log("=".repeat(60));
|
|
console.log("🔧 Tool Execution:");
|
|
console.log(` 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("");
|
|
|
|
auditLog("http_server_started", "system", {
|
|
port,
|
|
host,
|
|
url: serverUrl,
|
|
});
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop the HTTP server
|
|
*/
|
|
async stop() {
|
|
if (this.server) {
|
|
return new Promise((resolve) => {
|
|
this.server.close(() => {
|
|
logger.info("HTTP Server stopped");
|
|
auditLog("http_server_stopped", "system", {});
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main execution function
|
|
*/
|
|
async function main() {
|
|
console.log("🚀 Starting Laravel Healthcare MCP HTTP Server...");
|
|
const httpServer = new HttpServer();
|
|
|
|
try {
|
|
// Initialize and start HTTP server
|
|
console.log("📋 Step 1: Initializing server...");
|
|
await httpServer.initialize();
|
|
|
|
console.log("📋 Step 2: Starting HTTP server...");
|
|
await httpServer.start();
|
|
|
|
// Graceful shutdown
|
|
const shutdown = async (signal) => {
|
|
logger.info(`Received ${signal}, shutting down HTTP server...`);
|
|
await httpServer.stop();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
process.on("SIGUSR2", () => shutdown("SIGUSR2"));
|
|
} catch (error) {
|
|
console.error("❌ HTTP Server startup failed:", error.message);
|
|
console.error("Stack trace:", error.stack);
|
|
logger.error("HTTP Server startup failed:", error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
main().catch((error) => {
|
|
console.error("Fatal error:", error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
export { HttpServer };
|