UNPKG

mcp-vessel-accounts

Version:

MCP server for vessel account management including EyeShare API integration, vessel expenses, and purchase orders

312 lines 12.6 kB
import { logger } from "../utils/logger.js"; import { eyeShareApi } from "../utils/api.js"; import { mongodbTools } from "./mongodb.js"; export class ToolHandler { constructor(server) { this.server = server; } async handleCallTool(name, arguments_) { logger.info(`Handling tool call: ${name}`, { arguments: arguments_ }); try { switch (name) { case "get_vessels": return await this.getVessels(arguments_); case "search_invoices": return await this.searchInvoices(arguments_); case "download_attachment": return await this.downloadAttachment(arguments_); // MongoDB Database Tools case "vessel_expenses": return await this.handleVesselExpenses(arguments_); case "vessel_expenses_previous_year": return await this.handleVesselExpensesPreviousYear(arguments_); case "purchase_orders": return await this.handlePurchaseOrders(arguments_); case "get_purchase_order_by_invoice": return await this.getPurchaseOrderByInvoice(arguments_); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error(`Error handling tool call ${name}:`, error); throw error; } } async getVessels(arguments_) { logger.info("Getting vessels..."); const vessels = await eyeShareApi.getVessels(); return [{ type: "text", text: JSON.stringify(vessels, null, 2) }]; } async searchInvoices(arguments_) { logger.info("Searching invoices...", arguments_); const searchRequest = { Page: { Skip: arguments_.skip || 0, Limit: arguments_.limit || 10000 }, Filters: { Equality: [], Contains: [], OpenInterval: [], ClosedInterval: [], Regex: [], Arrays: [], SubSet: [], TextIndex: [], StartsWith: [], WildCard: [], Features: { ExtendedAccess: true, View: "search" } }, Sort: [], CountOnly: false }; // If invoiceId is provided, search for specific invoice if (arguments_.invoiceId) { searchRequest.Filters.Equality.push({ Field: "Id", StringValue: arguments_.invoiceId, ExactMatch: true }); } // Otherwise, add vessel filter if provided else if (arguments_.vesselKey || arguments_.vesselCode) { const vesselCode = arguments_.vesselKey || arguments_.vesselCode; searchRequest.Filters.SubSet.push({ FilterSet: { Equality: [], Contains: [], OpenInterval: [], ClosedInterval: [], Regex: [], Arrays: [], SubSet: [], TextIndex: [], StartsWith: [ { Field: "Head.Ship.Key", Value: vesselCode, CaseSensitive: false, AllowStartWithWildcard: false }, { Field: "Head.Ship.Description", Value: vesselCode, CaseSensitive: false, AllowStartWithWildcard: false } ], WildCard: [] }, Or: true }); } // Add credit note filter if provided if (arguments_.creditNote !== undefined) { searchRequest.Filters.Equality.push({ Field: "Head.CreditNote", BoolValue: arguments_.creditNote }); } // Add vessel code filter if provided (CompanyCode field) if (arguments_.vesselCode) { searchRequest.Filters.Contains.push({ Field: "CompanyCode", StringList: [arguments_.vesselCode] }); } // Add date range filter if provided if (arguments_.fromDate || arguments_.toDate) { const dateField = arguments_.dateField || "Head.InvoiceDate"; searchRequest.Filters.ClosedInterval.push({ Field: dateField, FromDateTimeValue: arguments_.fromDate || "", ToDateTimeValue: arguments_.toDate || "" }); } // Add amount range filter if provided if (arguments_.minAmount || arguments_.maxAmount) { searchRequest.Filters.ClosedInterval.push({ Field: "Head.TotalAmount", FromDateTimeValue: arguments_.minAmount?.toString() || "", ToDateTimeValue: arguments_.maxAmount?.toString() || "" }); } // Add status filter if provided if (arguments_.status) { searchRequest.Filters.Equality.push({ Field: "Head.Status", StringValue: arguments_.status, ExactMatch: true }); } // Add supplier filter if provided if (arguments_.supplier) { searchRequest.Filters.Contains.push({ Field: "Head.Supplier.Name", StringValue: arguments_.supplier, ExactMatch: false }); } // Add currency filter if provided if (arguments_.currency) { searchRequest.Filters.SubSet.push({ FilterSet: { Equality: [{ Field: "Head.Currency.Key", StringValue: arguments_.currency, ExactMatch: false }], Contains: [], OpenInterval: [], ClosedInterval: [], Regex: [], Arrays: [], SubSet: [], TextIndex: [], StartsWith: [], WildCard: [] } }); } // Add urgent filter if provided if (arguments_.urgent !== undefined) { searchRequest.Filters.SubSet.push({ FilterSet: { Equality: [{ Field: "Head.Urgent", StringValue: arguments_.urgent ? "YES" : "NO" }], Contains: [], OpenInterval: [], ClosedInterval: [], Regex: [], Arrays: [], SubSet: [], TextIndex: [], StartsWith: [], WildCard: [] }, Or: true }); } // Add invoice number filter if provided if (arguments_.invoiceNumber) { searchRequest.Filters.StartsWith.push({ Field: "Head.InvoiceNumber", Value: arguments_.invoiceNumber, AllowStartWithWildcard: false }); } const result = await eyeShareApi.searchInvoices(searchRequest); return [{ type: "text", text: JSON.stringify(result, null, 2) }]; } async downloadAttachment(arguments_) { logger.info("Downloading attachment...", arguments_); if (!arguments_.attachmentId || !arguments_.documentId) { throw new Error("attachmentId and documentId are required"); } const attachmentData = await eyeShareApi.getAttachment(arguments_.attachmentId, arguments_.documentId, arguments_.version || 0); // Convert to base64 for safe transmission const base64Data = attachmentData.toString('base64'); const attachmentInfo = { attachmentId: arguments_.attachmentId, documentId: arguments_.documentId, version: arguments_.version || 0, data: base64Data, size: attachmentData.length }; return [{ type: "text", text: JSON.stringify(attachmentInfo, null, 2) }]; } // MongoDB Database Tool Handlers async handleVesselExpenses(arguments_) { const vesselTool = mongodbTools.find(tool => tool.name === 'vessel_expenses'); if (!vesselTool) { throw new Error('Vessel expenses tool not found'); } const result = await vesselTool.execute(arguments_); return result.content; } async handleVesselExpensesPreviousYear(arguments_) { const vesselTool = mongodbTools.find(tool => tool.name === 'vessel_expenses_previous_year'); if (!vesselTool) { throw new Error('Vessel expenses previous year tool not found'); } const result = await vesselTool.execute(arguments_); return result.content; } async handlePurchaseOrders(arguments_) { const purchaseOrderTool = mongodbTools.find(tool => tool.name === 'purchase_orders'); if (!purchaseOrderTool) { throw new Error('Purchase orders tool not found'); } const result = await purchaseOrderTool.execute(arguments_); return result.content; } async getPurchaseOrderByInvoice(arguments_) { logger.info("Getting purchase order by invoice...", arguments_); const extractPurchaseOrdersOnly = arguments_.extractPurchaseOrdersOnly !== false; // Default to true // Check if this is parallel processing (multiple invoices) if (arguments_.invoiceConfigs && Array.isArray(arguments_.invoiceConfigs)) { if (arguments_.invoiceConfigs.length === 0) { throw new Error("invoiceConfigs array cannot be empty"); } // Validate each config for (const config of arguments_.invoiceConfigs) { if (!config.invoiceId || !config.vesselCode) { throw new Error("Each invoiceConfig must have invoiceId and vesselCode"); } } const maxWorkers = Math.min(arguments_.maxWorkers || 500, 500); logger.info(`Starting parallel processing for ${arguments_.invoiceConfigs.length} invoices with ${maxWorkers} workers`); const result = await eyeShareApi.getPurchaseOrdersByInvoicesParallel(arguments_.invoiceConfigs, maxWorkers); // Format the response for better readability const response = { summary: result.summary, results: result.results, errors: result.errors.length > 0 ? result.errors : undefined }; return [{ type: "text", text: JSON.stringify(response, null, 2) }]; } // Single invoice processing (original functionality) if (!arguments_.invoiceId || !arguments_.vesselCode) { throw new Error("For single invoice processing: invoiceId and vesselCode are required. For parallel processing: use invoiceConfigs array."); } const result = await eyeShareApi.getPurchaseOrderByInvoice(arguments_.invoiceId, arguments_.vesselCode); // Extract only PurchaseOrders if requested if (extractPurchaseOrdersOnly) { const purchaseOrders = result.PurchaseOrders || []; const response = { invoiceId: arguments_.invoiceId, vesselCode: arguments_.vesselCode, purchaseOrdersCount: purchaseOrders.length, purchaseOrders: purchaseOrders }; return [{ type: "text", text: JSON.stringify(response, null, 2) }]; } // Return full response if extraction is disabled return [{ type: "text", text: JSON.stringify(result, null, 2) }]; } } //# sourceMappingURL=index.js.map