Skip to main content
Glama
server.ts.backupβ€’24.1 kB
#!/usr/bin/env node import express from "express"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, McpError, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { AgentService } from "./core/agent-service.js"; import { PaymentManager } from "./payments/payment-manager.js"; import { createAgentTools, validateToolArguments, handleCreateAgent, handleListAgents, handleGetAgent, handlePromptAgent } from "./tools/agent-tools.js"; import { PlatformAPIClient } from "./platform/api-client.js"; import { handlePlatformError } from "./platform/error-handler.js"; import { atxpServer } from '@atxp/server'; console.log("πŸš€ MoluAbi MCP Server starting..."); console.log("πŸ”„ SECURITY UPDATE: Now using API key authentication with platform integration"); // Initialize services const paymentMode = (process.env.PAYMENT_MODE as "none" | "atxp" | "subscription") || "none"; const paymentManager = new PaymentManager(paymentMode); const agentService = new AgentService(); const platformClient = new PlatformAPIClient(process.env.PLATFORM_API_URL || 'https://app.moluabi.com'); async function main() { try { // Initialize payment system await paymentManager.initialize(); // Create HTTP server const app = express(); const PORT = parseInt(process.env.PORT || '5000', 10); // Configure ATXP server setup for payment context const PAYMENT_DESTINATION = process.env.PAYMENT_DESTINATION; let atxpMcpServer: any = null; let atxpTransport: any = null; if (PAYMENT_DESTINATION && paymentMode === 'atxp') { console.log('πŸ”§ Setting up ATXP MCP server...'); // Create dedicated MCP server for ATXP atxpMcpServer = new Server( { name: "moluabi-atxp-server", version: "2.0.0", }, { capabilities: { tools: {}, }, } ); // Create ATXP transport for MCP atxpTransport = new StreamableHTTPServerTransport(); // Register tools on ATXP server with payment requirements atxpMcpServer.setRequestHandler(ListToolsRequestSchema, async () => { console.log('πŸ“‹ ATXP MCP list_tools called'); return { tools }; }); // Handle tool execution with ATXP payments atxpMcpServer.setRequestHandler(CallToolRequestSchema, async (request: any) => { const { name, arguments: args } = request.params; console.log(`πŸ”§ ATXP tool call: ${name}`); const toolPricing: Record<string, string> = { "create_agent": "0.05", "list_agents": "0.001", "get_agent": "0.001", "update_agent": "0.02", "delete_agent": "0.01", "prompt_agent": "0.01", "add_user_to_agent": "0.005", "remove_user_from_agent": "0.005", "get_usage_report": "0.002", "get_pricing": "0.001" }; if (toolPricing[name]) { console.log(`πŸ’° Requiring ATXP payment: $${toolPricing[name]} for ${name}`); try { await requirePayment(BigNumber(toolPricing[name])); console.log('βœ… ATXP payment validated successfully'); } catch (paymentError) { console.log(`❌ ATXP payment failed:`, paymentError); throw new Error("Payment required - ATXP payment validation failed"); } } // Execute tool with dummy API key (ATXP doesn't use API keys) const atxpArgs = { ...args, apiKey: 'mab_atxp_dummy_key' }; switch (name) { case "create_agent": return await handleCreateAgent(atxpArgs); case "list_agents": return await handleListAgents(atxpArgs); case "get_agent": return await handleGetAgent(atxpArgs); case "prompt_agent": return await handlePromptAgent(atxpArgs); case "get_pricing": return await agentService.getPricing(); default: throw new Error(`Tool not implemented: ${name}`); } }); // Connect ATXP server to transport await atxpMcpServer.connect(atxpTransport); // Apply ATXP middleware globally for payment context app.use(atxpServer({ destination: PAYMENT_DESTINATION, payeeName: 'MoluAbi MCP Server', })); console.log('βœ… ATXP MCP server and middleware configured'); } // Health check endpoint app.get('/', (req, res) => { res.json({ status: 'healthy', service: 'MoluAbi MCP Server', version: '2.0.0', authentication: 'API Key (mab_...)', timestamp: new Date().toISOString(), debug: 'CODE_IS_ACTUALLY_RUNNING_NOW' }); }); // ATXP SDK Compatibility: Route root POST requests to /mcp/call app.post('/', express.json(), async (req, res) => { console.log('πŸ”„ ATXP SDK compatibility: Routing root POST to /mcp/call'); // Accept both "tool" and "name" parameters for backward compatibility const toolName = req.body.tool || req.body.name; const args = req.body.arguments; try { if (!toolName) { return res.status(400).json({ error: 'Missing tool/name parameter' }); } // Validate arguments using the same validation as MCP validateToolArguments(toolName, args); // All tools now use API key authentication if (!args.apiKey) { return res.status(400).json({ error: 'API key required', message: 'All operations require a valid MoluAbi API key (format: mab_...)' }); } let result; // Handle tool calls with new API key authentication switch (toolName) { case "create_agent": result = await handleCreateAgent(args); break; case "list_agents": result = await handleListAgents(args); break; case "get_agent": result = await handleGetAgent(args); break; case "prompt_agent": result = await handlePromptAgent(args); break; case "update_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } const agent = await platformClient.updateAgent(args.apiKey, args.agentId, { name: args.name, description: args.description, instructions: args.instructions, type: args.type, isPublic: args.isPublic, isShareable: args.isShareable }); result = { success: true, agent, cost: 0.02, operation: "update_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'update_agent'); } break; case "delete_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.deleteAgent(args.apiKey, args.agentId); result = { success: true, message: "Agent deleted successfully", cost: 0.01, operation: "delete_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'delete_agent'); } break; case "add_user_to_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.addUserToAgent(args.apiKey, args.agentId, args.userEmail); result = { success: true, message: `Access granted to ${args.userEmail}`, cost: 0.005, operation: "add_user_to_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'add_user_to_agent'); } break; case "remove_user_from_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.removeUserFromAgent(args.apiKey, args.agentId, args.userEmail); result = { success: true, message: `Access removed for ${args.userEmail}`, cost: 0.005, operation: "remove_user_from_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'remove_user_from_agent'); } break; case "get_usage_report": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } const report = await platformClient.getUsageReport(args.apiKey, args.days); result = { success: true, report, cost: 0.002, operation: "get_usage_report", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'get_usage_report'); } break; case "get_pricing": try { const pricing = await agentService.getPricing(); result = { success: true, pricing, cost: 0.001, operation: "get_pricing" }; } catch (error) { result = handlePlatformError(error, 'get_pricing'); } break; default: return res.status(400).json({ error: `Unknown tool: ${toolName}` }); } // Record usage if payment system is enabled and operation was successful if (result.success && result.cost > 0 && args.apiKey) { try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (keyValidation.valid && keyValidation.userId) { await paymentManager.recordUsage(keyValidation.userId, toolName, result.cost); } } catch (error) { console.warn('⚠️ Failed to record usage:', error); } } res.json(result); } catch (error) { console.error(`❌ Error executing root endpoint tool ${toolName}:`, error); res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' }); } }); // MCP HTTP Endpoints for developers app.get('/tools', (req, res) => { const tools = createAgentTools(); res.json({ tools: tools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })) }); }); app.get('/pricing', async (req, res) => { try { const pricing = await agentService.getPricing(); res.json(pricing); } catch (error) { res.status(500).json({ error: 'Failed to get pricing', message: error instanceof Error ? error.message : 'Unknown error' }); } }); // Standard OAuth Resource Server Metadata Endpoint (ATXP SDK expects this path) app.get('/.well-known/oauth-protected-resource', (req, res) => { console.log('πŸ” OAuth resource metadata requested (standard path)'); res.json({ resource: "https://moluabi-mcp-server.replit.app", authorization_servers: ["https://auth.atxp.ai"], authorization_server: "https://auth.atxp.ai", issuer: "https://auth.atxp.ai", authorization_endpoint: "https://auth.atxp.ai/oauth/authorize", token_endpoint: "https://auth.atxp.ai/oauth/token", scopes_supported: ["mcp:tools", "mcp:read", "mcp:write"], token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], resource_server_metadata: { name: "MoluAbi ATXP MCP Server", version: "2.0.0", supported_operations: ["agent_management", "tool_execution"] } }); }); // Alternative OAuth Resource Server Metadata Endpoint for ATXP (backward compatibility) app.get('/.well-known/oauth-protected-resource/atxp', (req, res) => { console.log('πŸ” OAuth resource metadata requested for ATXP (legacy path)'); res.json({ resource: "https://moluabi-mcp-server.replit.app", authorization_servers: ["https://auth.atxp.ai"], authorization_server: "https://auth.atxp.ai", issuer: "https://auth.atxp.ai", authorization_endpoint: "https://auth.atxp.ai/oauth/authorize", token_endpoint: "https://auth.atxp.ai/oauth/token", scopes_supported: ["mcp:tools", "mcp:read", "mcp:write"], token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], resource_server_metadata: { name: "MoluAbi ATXP MCP Server", version: "2.0.0", supported_operations: ["agent_management", "tool_execution"] } }); }); app.post('/mcp/call', express.json(), async (req, res) => { // Accept both "tool" and "name" parameters for backward compatibility const toolName = req.body.tool || req.body.name; const args = req.body.arguments; try { if (!toolName) { return res.status(400).json({ error: 'Missing tool/name parameter' }); } // Validate arguments using the same validation as MCP validateToolArguments(toolName, args); // All tools now use API key authentication if (!args.apiKey) { return res.status(400).json({ error: 'API key required', message: 'All operations require a valid MoluAbi API key (format: mab_...)' }); } let result; // Handle tool calls with new API key authentication switch (toolName) { case "create_agent": result = await handleCreateAgent(args); break; case "list_agents": result = await handleListAgents(args); break; case "get_agent": result = await handleGetAgent(args); break; case "prompt_agent": result = await handlePromptAgent(args); break; case "update_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } const agent = await platformClient.updateAgent(args.apiKey, args.agentId, { name: args.name, description: args.description, instructions: args.instructions, type: args.type, isPublic: args.isPublic, isShareable: args.isShareable }); result = { success: true, agent, cost: 0.02, operation: "update_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'update_agent'); } break; case "delete_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.deleteAgent(args.apiKey, args.agentId); result = { success: true, message: `Agent ${args.agentId} deleted successfully`, cost: 0.01, operation: "delete_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'delete_agent'); } break; case "add_user_to_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.addUserToAgent(args.apiKey, args.agentId, args.userEmail); result = { success: true, message: `Access granted to ${args.userEmail}`, cost: 0.005, operation: "add_user_to_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'add_user_to_agent'); } break; case "remove_user_from_agent": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } await platformClient.removeUserFromAgent(args.apiKey, args.agentId, args.userEmail); result = { success: true, message: `Access removed for ${args.userEmail}`, cost: 0.005, operation: "remove_user_from_agent", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'remove_user_from_agent'); } break; case "get_usage_report": try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (!keyValidation.valid) { result = { success: false, error: "Invalid API key", cost: 0 }; break; } const report = await platformClient.getUsageReport(args.apiKey, args.days); result = { success: true, report, cost: 0.002, operation: "get_usage_report", organizationId: keyValidation.organizationId }; } catch (error) { result = handlePlatformError(error, 'get_usage_report'); } break; case "get_pricing": try { const pricing = await agentService.getPricing(); result = { success: true, pricing, cost: 0.001, operation: "get_pricing" }; } catch (error) { result = handlePlatformError(error, 'get_pricing'); } break; default: return res.status(400).json({ error: `Unknown tool: ${toolName}` }); } // Record usage if payment system is enabled and operation was successful if (result.success && result.cost > 0 && args.apiKey) { try { const keyValidation = await platformClient.validateAPIKey(args.apiKey); if (keyValidation.valid && keyValidation.userId) { await paymentManager.recordUsage(keyValidation.userId, toolName, result.cost); } } catch (error) { console.warn('⚠️ Failed to record usage:', error); } } res.json(result); } catch (error) { console.error(`❌ Error executing HTTP tool ${toolName}:`, error); res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' }); } }); // ATXP Endpoint - Use transport delegation pattern if (PAYMENT_DESTINATION && paymentMode === 'atxp' && atxpTransport) { app.post('/atxp', express.json(), async (req, res) => { console.log('πŸ”„ ATXP MCP request received:', req.body); // Set proper headers for MCP protocol res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // Handle specific MCP methods that ATXP SDK requires const { jsonrpc, method, params, id } = req.body; if (!jsonrpc || jsonrpc !== "2.0") { return res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid JSON-RPC request" }, id: id || null }); } try { if (method === "initialize") { console.log('πŸ› οΈ MCP initialize called on ATXP'); return res.json({ jsonrpc: "2.0", result: { protocolVersion: "2024-11-05", capabilities: { tools: {}, logging: {} }, serverInfo: { name: "moluabi-atxp-server", version: "2.0.0" } }, id }); } else { // Let the MCP transport handle other methods (tools/list, tools/call) await atxpTransport.handleRequest(req, res, req.body); } } catch (error) { console.error('❌ Error handling ATXP MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); } // Start HTTP server app.listen(PORT, '0.0.0.0', () => { console.log(`🌐 HTTP server listening on port ${PORT}`); console.log('πŸ“ Available endpoints:'); console.log(' GET / - Health check'); console.log(' POST / - Root tool execution (API Key method)'); console.log(' POST /mcp/call - MCP tool execution (API Key method)'); console.log(' POST /atxp - ATXP MCP endpoint (OAuth2 + Payment)'); console.log('πŸ”‘ API Key method: https://moluabi-mcp-server.replit.app/mcp/call'); console.log('πŸ” ATXP method: https://moluabi-mcp-server.replit.app/atxp'); }); } }

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/oregpt/moluabi-mcp-server'

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