qbo-mcp-ts
Version:
TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support
530 lines • 19.9 kB
JavaScript
;
/**
* 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