UNPKG

qbo-mcp-ts

Version:

TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support

530 lines 19.9 kB
"use strict"; /** * Main MCP Server implementation for QBOMCP-TS */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.QBOMCPServer = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const client_1 = require("./api/client"); const invoice_1 = require("./services/invoice"); const config_1 = require("./utils/config"); const logger_1 = require("./utils/logger"); const cache_1 = require("./services/cache"); const queue_1 = require("./services/queue"); const types_1 = require("./types"); const crypto = __importStar(require("crypto")); /** * QBOMCP-TS Server */ class QBOMCPServer { server; api; invoiceService; tools = []; initialized = false; constructor() { // Initialize server this.server = new index_js_1.Server({ name: 'qbomcp-ts', version: '2.0.0', }, { capabilities: { tools: {}, resources: {}, }, }); // Initialize API client this.api = new client_1.QBOApiClient(); // Initialize services this.invoiceService = new invoice_1.InvoiceService(this.api); // Set up handlers this.setupHandlers(); logger_1.logger.info('QBOMCP-TS Server initialized'); } /** * Set up MCP protocol handlers */ setupHandlers() { // Tool handlers this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: this.getToolDefinitions(), })); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const requestId = crypto.randomUUID(); logger_1.logger.setRequestId(requestId); try { logger_1.logger.tool(request.params.name, 'start', { arguments: request.params.arguments, }); const result = await this.handleToolCall(request.params.name, request.params.arguments || {}); logger_1.logger.tool(request.params.name, 'complete'); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { logger_1.logger.tool(request.params.name, 'error', { error: error.message }); if (error instanceof types_1.ValidationError) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, error.message, error.details); } throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, error.message || 'An error occurred'); } finally { logger_1.logger.clearRequestId(); } }); // Resource handlers this.server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({ resources: this.getResourceDefinitions(), })); this.server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => { const resource = await this.handleResourceRead(request.params.uri); return { contents: [ { type: 'text', text: JSON.stringify(resource, null, 2), uri: request.params.uri, }, ], }; }); } /** * Get tool definitions */ getToolDefinitions() { return [ // Invoice Tools { name: 'get_invoices', description: 'Get invoices with natural language filtering. Say things like "unpaid invoices" or "invoices for John Smith"', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['unpaid', 'paid', 'overdue', 'all'], description: 'Invoice status filter', }, customerName: { type: 'string', description: 'Filter by customer name', }, dateFrom: { type: 'string', description: 'Start date (e.g., "last month", "2024-01-01")', }, dateTo: { type: 'string', description: 'End date', }, minAmount: { type: 'number', description: 'Minimum invoice amount', }, maxAmount: { type: 'number', description: 'Maximum invoice amount', }, limit: { type: 'number', description: 'Number of results to return (max 100)', }, }, }, }, { name: 'create_invoice', description: 'Create a new invoice for a customer', inputSchema: { type: 'object', required: ['customerName', 'items'], properties: { customerName: { type: 'string', description: 'Customer name (must exist in QuickBooks)', }, items: { type: 'array', description: 'Line items for the invoice', items: { type: 'object', required: ['description', 'amount'], properties: { description: { type: 'string', description: 'Item description', }, amount: { type: 'number', description: 'Item amount', }, quantity: { type: 'number', description: 'Quantity (optional)', }, unitPrice: { type: 'number', description: 'Unit price (optional)', }, }, }, }, dueDate: { type: 'string', description: 'Due date (optional, defaults to 30 days)', }, memo: { type: 'string', description: 'Invoice memo/notes', }, emailToCustomer: { type: 'boolean', description: 'Send invoice via email immediately', }, }, }, }, { name: 'send_invoice', description: 'Email an invoice to customer', inputSchema: { type: 'object', required: ['invoiceId'], properties: { invoiceId: { type: 'string', description: 'Invoice ID to send', }, email: { type: 'string', description: 'Email address (optional, uses customer default)', }, subject: { type: 'string', description: 'Email subject (optional)', }, message: { type: 'string', description: 'Email message (optional)', }, }, }, }, { name: 'get_invoice_aging', description: 'Get accounts receivable aging report', inputSchema: { type: 'object', properties: { asOfDate: { type: 'string', description: 'As of date for the report', }, }, }, }, // Help and Info Tools { name: 'help', description: 'Get help with using the QuickBooks MCP server', inputSchema: { type: 'object', properties: { topic: { type: 'string', description: 'Help topic (e.g., "invoices", "expenses", "reports")', }, }, }, }, { name: 'get_api_status', description: 'Check QuickBooks API connection and rate limits', inputSchema: { type: 'object', properties: {}, }, }, ]; } /** * Get resource definitions */ getResourceDefinitions() { return [ { uri: 'qbo://company/info', name: 'Company Information', description: 'Current QuickBooks company information', mimeType: 'application/json', }, { uri: 'qbo://cache/stats', name: 'Cache Statistics', description: 'Cache performance statistics', mimeType: 'application/json', }, { uri: 'qbo://queue/stats', name: 'Queue Statistics', description: 'API queue statistics', mimeType: 'application/json', }, ]; } /** * Handle tool calls */ async handleToolCall(name, args) { const timer = logger_1.logger.startTimer(); try { switch (name) { // Invoice tools case 'get_invoices': const invoices = await this.invoiceService.getInvoices(args); return { success: true, data: invoices, metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 1, cached: false, }, }; case 'create_invoice': const created = await this.invoiceService.createInvoice(args); return { success: true, data: { message: 'Invoice created successfully', invoice: { id: created.Id, number: created.DocNumber, total: created.TotalAmt, customer: created.CustomerRef.name, }, }, metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 2, }, }; case 'send_invoice': await this.invoiceService.sendInvoice(args); return { success: true, data: { message: 'Invoice sent successfully', }, metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 1, }, }; case 'get_invoice_aging': const aging = await this.invoiceService.getAgingReport(); return { success: true, data: aging, metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 1, }, }; // Help tools case 'help': return { success: true, data: this.getHelp(args.topic), metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 0, }, }; case 'get_api_status': const limits = await this.api.getApiLimits(); const cacheStats = cache_1.cacheService.getStats(); const queueStats = queue_1.queueService.getStats(); return { success: true, data: { api: { connected: true, environment: config_1.config.getQBOConfig().environment, limits, }, cache: cacheStats, queue: queueStats, }, metadata: { timestamp: new Date().toISOString(), requestId: crypto.randomUUID(), apiCalls: 1, }, }; default: throw new types_1.ValidationError(`Unknown tool: ${name}`); } } finally { const duration = timer(); logger_1.logger.performance(`Tool: ${name}`, duration); } } /** * Handle resource reads */ async handleResourceRead(uri) { switch (uri) { case 'qbo://company/info': return await this.api.getCompanyInfo(); case 'qbo://cache/stats': return cache_1.cacheService.getStats(); case 'qbo://queue/stats': return queue_1.queueService.getStats(); default: throw new Error(`Unknown resource: ${uri}`); } } /** * Get help information */ getHelp(topic) { const topics = { invoices: { description: 'Invoice management help', examples: [ 'get_invoices with status:"unpaid"', 'create_invoice for "ABC Company" with items', 'send_invoice with invoiceId', 'get_invoice_aging for receivables report', ], tips: [ 'Use natural language for dates like "last month"', 'Customer names must match exactly', 'Invoices default to 30-day payment terms', ], }, general: { description: 'General help', availableTopics: ['invoices', 'expenses', 'reports', 'customers'], tips: [ 'All dates support natural language', 'Results are cached for performance', 'API rate limits are managed automatically', ], }, }; return topics[topic] || topics.general; } /** * Get the MCP server instance */ getServer() { return this.server; } /** * Shutdown the server */ async shutdown() { logger_1.logger.info('Shutting down QBOMCP-TS server'); await Promise.all([cache_1.cacheService.shutdown(), queue_1.queueService.shutdown()]); await this.server.close(); logger_1.logger.info('Server shutdown complete'); } /** * Initialize the server */ async initialize() { if (!this.initialized) { await this.setupTools(); this.initialized = true; } } /** * Get server info */ getServerInfo() { return { name: 'qbomcp-ts', version: '2.0.0', capabilities: { tools: {}, resources: {}, }, }; } /** * Get registered tools */ getTools() { return this.tools; } /** * Get transport type */ getTransport() { return 'stdio'; } /** * Setup tools (can be mocked in tests) */ async setupTools() { this.tools = [ { name: 'create_invoice', description: 'Create a new invoice' }, { name: 'get_invoice', description: 'Get invoice by ID' }, { name: 'list_invoices', description: 'List invoices' }, { name: 'update_invoice', description: 'Update invoice' }, { name: 'delete_invoice', description: 'Delete invoice' }, { name: 'send_invoice', description: 'Send invoice' }, ]; } } exports.QBOMCPServer = QBOMCPServer; //# sourceMappingURL=server.js.map