Skip to main content
Glama
index.ts11.3 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { BTCPayServerClient } from './utils/btcpay-client.js'; import { BTCPayServerConfig } from './types.js'; import { ServiceRegistry } from './services/service-registry.js'; class BTCPayServerMCP { private server: Server; private client: BTCPayServerClient | null = null; private serviceRegistry: ServiceRegistry | null = null; constructor() { this.server = new Server( { name: 'btcpayserver-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupErrorHandling(); this.setupToolHandlers(); } private getClient(): BTCPayServerClient { if (!this.client) { const baseUrl = process.env.BTCPAY_BASE_URL; const apiKey = process.env.BTCPAY_API_KEY; const storeId = process.env.BTCPAY_STORE_ID; if (!baseUrl || !apiKey) { throw new McpError( ErrorCode.InvalidRequest, 'BTCPayServer configuration missing. Please set BTCPAY_BASE_URL and BTCPAY_API_KEY environment variables.' ); } const config: BTCPayServerConfig = { baseUrl, apiKey, storeId }; this.client = new BTCPayServerClient(config); this.serviceRegistry = new ServiceRegistry(this.client); } return this.client; } private getServiceRegistry(): ServiceRegistry { if (!this.serviceRegistry) { // This will initialize both client and service registry this.getClient(); } return this.serviceRegistry!; } private setupErrorHandling(): void { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Service Discovery Tools (Square MCP Pattern) { name: 'get_service_info', description: 'Discover available BTCPayServer services and their methods. Use this to explore what operations are available.', inputSchema: { type: 'object', properties: { serviceName: { type: 'string', description: 'Optional: Get info for a specific service (e.g., "payment-requests", "invoices", "lightning"). If not provided, lists all services.' } } } }, { name: 'get_method_info', description: 'Get detailed parameter requirements and examples for a specific service method.', inputSchema: { type: 'object', properties: { serviceName: { type: 'string', description: 'Service name (e.g., "payment-requests", "invoices", "lightning")' }, methodName: { type: 'string', description: 'Method name (e.g., "create", "get", "list", "delete")' } }, required: ['serviceName', 'methodName'] } }, { name: 'btcpay_request', description: 'Execute a BTCPayServer API operation using the service-based approach.', inputSchema: { type: 'object', properties: { serviceName: { type: 'string', description: 'Service name (e.g., "payment-requests", "invoices", "lightning")' }, methodName: { type: 'string', description: 'Method name (e.g., "create", "get", "list", "delete")' }, parameters: { type: 'object', description: 'Parameters for the method (use get_method_info to see required parameters)' } }, required: ['serviceName', 'methodName'] } } ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { this.getClient(); // Initialize client const serviceRegistry = this.getServiceRegistry(); switch (name) { // Service Discovery Tools (Square MCP Pattern) case 'get_service_info': { const serviceName = args?.serviceName as string; if (serviceName) { // Get info for a specific service const service = serviceRegistry.getService(serviceName); if (!service) { return { content: [ { type: 'text', text: `Service "${serviceName}" not found.\n\nAvailable services: ${serviceRegistry.getServiceNames().join(', ')}` } ] }; } const serviceInfo = service.getServiceInfo(); return { content: [ { type: 'text', text: `**${serviceInfo.name}** (Category: ${serviceInfo.category})\n\n${serviceInfo.description}\n\n**Available Methods:**\n${serviceInfo.methods.map(m => `• **${m.name}**: ${m.description}`).join('\n')}\n\nUse get_method_info to see detailed parameters for each method.` } ] }; } else { // List all services const allServices = serviceRegistry.getAllServiceInfo(); const servicesByCategory = allServices.reduce((acc, service) => { if (!acc[service.category]) acc[service.category] = []; acc[service.category].push(service); return acc; }, {} as Record<string, any[]>); let output = "**BTCPayServer Services Directory**\n\n"; for (const [category, services] of Object.entries(servicesByCategory)) { output += `**${category.toUpperCase()}:**\n`; services.forEach(service => { output += `• **${service.name}**: ${service.description} (${service.methods.length} methods)\n`; }); output += '\n'; } output += "Use get_service_info with a specific service name to see available methods."; return { content: [ { type: 'text', text: output } ] }; } } case 'get_method_info': { const serviceName = args?.serviceName as string; const methodName = args?.methodName as string; const service = serviceRegistry.getService(serviceName); if (!service) { return { content: [ { type: 'text', text: `Service "${serviceName}" not found.\n\nAvailable services: ${serviceRegistry.getServiceNames().join(', ')}` } ] }; } const serviceInfo = service.getServiceInfo(); const method = serviceInfo.methods.find(m => m.name === methodName); if (!method) { return { content: [ { type: 'text', text: `Method "${methodName}" not found in service "${serviceName}".\n\nAvailable methods: ${serviceInfo.methods.map(m => m.name).join(', ')}` } ] }; } let output = `**${serviceName}.${methodName}**\n\n${method.description}\n\n`; output += "**Parameters:**\n"; for (const [paramName, paramDef] of Object.entries(method.parameters)) { const required = paramDef.required ? " (required)" : " (optional)"; const defaultVal = paramDef.default !== undefined ? ` [default: ${paramDef.default}]` : ""; output += `• **${paramName}** (${paramDef.type})${required}${defaultVal}: ${paramDef.description}\n`; } if (method.examples && method.examples.length > 0) { output += "\n**Examples:**\n"; method.examples.forEach((example, index) => { output += `\n**${index + 1}. ${example.name}**\n${example.description}\n\`\`\`json\n${JSON.stringify(example.parameters, null, 2)}\n\`\`\`\n`; }); } return { content: [ { type: 'text', text: output } ] }; } case 'btcpay_request': { const serviceName = args?.serviceName as string; const methodName = args?.methodName as string; const parameters = args?.parameters as Record<string, any> || {}; const service = serviceRegistry.getService(serviceName); if (!service) { return { content: [ { type: 'text', text: `Service "${serviceName}" not found.\n\nAvailable services: ${serviceRegistry.getServiceNames().join(', ')}` } ] }; } try { const result = await service.executeMethod(methodName, parameters); return { content: [ { type: 'text', text: `**${serviceName}.${methodName}** executed successfully:\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` } ] }; } catch (error) { return { content: [ { type: 'text', text: `**Error executing ${serviceName}.${methodName}:**\n\n${error instanceof Error ? error.message : String(error)}\n\nUse get_method_info to check parameter requirements.` } ] }; } } default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}` ); } }); } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('BTCPayServer MCP server running on stdio'); } } const server = new BTCPayServerMCP(); server.run().catch((error) => { console.error('Failed to start server:', error); process.exit(1); });

Implementation Reference

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/Abhijay007/btcpayserver-mcp'

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