first
This commit is contained in:
461
http-server.js
Normal file
461
http-server.js
Normal file
@@ -0,0 +1,461 @@
|
||||
#!/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 };
|
Reference in New Issue
Block a user