mcp-vessel-accounts
Version:
MCP server for vessel account management including EyeShare API integration, vessel expenses, and purchase orders
312 lines • 12.6 kB
JavaScript
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