UNPKG

defect-inspection-tools-mcp-server

Version:

Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control

1,077 lines 67.9 kB
import { logger } from "../utils/logger.js"; import { getTypesenseClient, getMongoClient } from "../utils/clients.js"; import { convertDefectDates, getDataLink, insertDataLinkToMongodb, getArtifact, fetchQaDetails } from "../utils/helpers.js"; import { config } from "../utils/config.js"; import axios from "axios"; import { ObjectId } from "mongodb"; export class ToolHandler { constructor(server) { this.server = server; } async handleCallTool(name, arguments_) { try { switch (name) { case "universal_defect_search": return await this.universalDefectSearch(arguments_); case "list_defects_by_stage": return await this.listDefectsByStage(arguments_); case "list_defects_by_inspection_type": return await this.listDefectsByInspectionType(arguments_); case "list_defects_by_report_type": return await this.listDefectsByReportType(arguments_); case "list_defects_by_status": return await this.listDefectsByStatus(arguments_); case "get_all_vessel_defects_records": return await this.getAllVesselDefectsRecords(arguments_); case "get_vessel_details": return await this.getVesselDetails(arguments_); case "get_defect_table_schema": return await this.getDefectTableSchema(arguments_); case "get_tmsa_summary": return await this.getTmsaSummary(arguments_); case "get_sire_reports_from_ocimf": return await this.getSireReportsFromOcimf(arguments_); case "fetch_fleet_historical_sire_observations": return await this.fetchFleetHistoricalSireObservations(arguments_); case "get_cdi_reports_from_ocimf": return await this.getCdiReportsFromOcimf(arguments_); case "get_vir_status_overview": return await this.getVirStatusOverview(arguments_); case "get_internal_audit_summary": return await this.getInternalAuditSummary(arguments_); case "get_psc_inspection_defects": return await this.getPscInspectionDefects(arguments_); case "get_summary_of_defects": return await this.getSummaryOfDefects(arguments_); case "google_search": return await this.googleSearch(arguments_); case "parse_document_link": return await this.parseDocumentLink(arguments_); case "write_casefile_data": return await this.writeCasefileData(arguments_); case "retrieve_casefile_data": return await this.retrieveCasefileData(arguments_); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error(`Error calling tool ${name}:`, error); throw error; } } // Core Defect Search Tools async universalDefectSearch(arguments_) { logger.info("universalDefectSearch called", arguments_); const collection = "defect"; const sessionId = arguments_.session_id || "testing"; const searchType = arguments_.search_type || "keyword"; const queryText = (arguments_.query || "").trim() || "*"; const filters = arguments_.filters || {}; const sortBy = arguments_.sort_by || "relevance"; const sortOrder = arguments_.sort_order || "desc"; const maxResults = arguments_.max_results || 25; try { // Compose `filter_by` string from filters const filterParts = []; if (filters && typeof filters === 'object') { for (const [key, value] of Object.entries(filters)) { if (value === null || value === undefined) { continue; } if (key.endsWith("_range")) { // Handle range filters const fieldBase = key.replace("_range", ""); const rangeValue = value; let minVal = rangeValue?.start_date; let maxVal = rangeValue?.end_date; // Convert date strings to Unix timestamps if (typeof minVal === 'string') { minVal = Math.floor(new Date(minVal).getTime() / 1000); } if (typeof maxVal === 'string') { maxVal = Math.floor(new Date(maxVal).getTime() / 1000); } if (minVal !== null && minVal !== undefined) { filterParts.push(`${fieldBase}:>=${minVal}`); } if (maxVal !== null && maxVal !== undefined) { filterParts.push(`${fieldBase}:<=${maxVal}`); } } else if (typeof value === 'boolean') { filterParts.push(`${key}:=${value.toString().toLowerCase()}`); } else if (typeof value === 'string') { filterParts.push(`${key}:=${JSON.stringify(value).replace(/"/g, '')}`); } else { filterParts.push(`${key}:=${value}`); } } } const filterBy = filterParts.length > 0 ? filterParts.join(" && ") : undefined; // Decide query behavior const q = searchType === "browse" ? "*" : queryText; const queryBy = "finding,inspectionType,reportType,inspectedBy,inspectionLocation,inspectingCompany"; const excludeFields = "_id,docId,fleetId,vesselId,fleetManagerId,technicalSuperintendentId,id,embedding"; // Sort expression let sortByExpr; if (sortBy !== "relevance") { sortByExpr = `${sortBy}:${sortOrder}`; } const query = { q: q, query_by: queryBy, per_page: maxResults, exclude_fields: excludeFields }; if (filterBy) { query.filter_by = filterBy; } if (sortByExpr) { query.sort_by = sortByExpr; } logger.debug(`[Typesense Query] ${JSON.stringify(query)}`); const client = getTypesenseClient(); const results = await client.client.collections(collection).documents().search(query); const hits = results.hits || []; const filteredHits = []; for (const hit of hits) { const document = hit.document || {}; if (typeof document === 'object' && document !== null && 'embedding' in document) { delete document.embedding; // Remove embedding field } const convertedDoc = convertDefectDates(document); filteredHits.push({ id: convertedDoc.id || convertedDoc._id, score: hit.text_match || 0, document: convertedDoc }); } const documents = filteredHits.map(hit => hit.document); const dataLink = await getDataLink(documents); const vesselName = hits.length > 0 ? hits[0].document?.vesselName : null; const linkHeader = `Smart defect search result for query: '${queryText}'`; await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, filters.imo, vesselName); const formattedResults = { found: results.found || 0, out_of: results.out_of || 0, page: results.page || 1, hits: filteredHits }; const content = { type: "text", text: JSON.stringify(formattedResults, null, 2), title: `Search results for '${collection}'`, format: "json" }; const artifactData = getArtifact("smart_defect_search", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Smart defect search artifact for query '${queryText}'`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Failure in smart defect search: ${error}`); throw error; } } async listDefectsByStage(arguments_) { logger.info("listDefectsByStage called", arguments_); const imo = arguments_.imo; const stage = arguments_.stage; const perPage = arguments_.per_page || 250; const sessionId = arguments_.session_id || "testing"; if (!imo || !stage) { throw new Error("IMO number and stage are required"); } try { const collection = "defect"; const includeFields = "imo,vesselName,referenceNo,currentStatus,closingDate,inspectedBy,inspectionType,reportDate,finding,riskCategory,correctiveAction,isExtended,stage,targetDate,inspectionCurrentStatus,inspectionTargetDate,findingsReference,reportType,areaOfConcern,purchaseRequisitionNo,purchaseOrderNo,latestReport,areaofConcern,source,inspectingCompany,inspectionLocation,viqNo,questions,nextDueDate,shipFlag,shipType,grossTonnage,yearBuilt,classSociety,detention,mouName,deadWeight,company,shipOnWatchlist/Alert,bannedShip,shipRiskDetails,callSign,mmsiNo,underperformingShip,types,certificates,deficiencydNumber,deficiencyCode,actionTaken,sNos,class,extendedDate"; const query = { q: "*", filter_by: `imo:${imo} && stage:${stage}`, per_page: perPage, include_fields: includeFields }; // Execute the search logger.info(`Searching for defects with stage '${stage}' for vessel ${imo}`); const client = getTypesenseClient(); const results = await client.client.collections(collection).documents().search(query); const hits = results.hits || []; const filteredHits = []; for (const hit of hits) { const document = hit.document || {}; // Remove embedding field to reduce response size if it exists if (typeof document === 'object' && document !== null && 'embedding' in document) { delete document.embedding; } // Convert date fields to human readable format const convertedDoc = convertDefectDates(document); filteredHits.push({ id: convertedDoc.id, score: hit.text_match || 0, document: convertedDoc }); } // Get documents for data link const documents = filteredHits.map(hit => hit.document); // Get data link const dataLink = await getDataLink(documents); // Get vessel name from hits let vesselName = null; try { vesselName = hits.length > 0 ? hits[0].document?.vesselName || null : null; } catch { vesselName = null; } // Insert the data link to mongodb collection const linkHeader = `defects with stage ${stage}`; await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, imo, vesselName || ""); // Format the results const formattedResults = { found: results.found || 0, out_of: results.out_of || 0, page: results.page || 1, hits: filteredHits }; // Convert the results to JSON string const formattedText = JSON.stringify(formattedResults, null, 2); // Create TextContent with all required fields in correct structure const content = { type: "text", text: formattedText, title: `Defects with stage '${stage}' for vessel ${imo}`, format: "json" }; const artifactData = getArtifact("list_defects_by_stage", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Defects with stage '${stage}' for vessel ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error retrieving defects with stage '${stage}' for vessel ${imo}:`, error); throw error; } } async listDefectsByInspectionType(arguments_) { logger.info("listDefectsByInspectionType called", arguments_); const imo = arguments_.imo; const inspectionType = arguments_.inspectionType; const stage = arguments_.stage || null; // Default to null for optional parameter const perPage = arguments_.per_page || 250; const sessionId = arguments_.session_id || "testing"; if (!imo || !inspectionType) { throw new Error("IMO number and inspection type are required"); } try { const collection = "defect"; const includeFields = "imo,vesselName,referenceNo,currentStatus,closingDate,inspectedBy,inspectionType,reportDate,finding,riskCategory,correctiveAction,isExtended,stage,targetDate,inspectionCurrentStatus,inspectionTargetDate,findingsReference,reportType,areaOfConcern,purchaseRequisitionNo,purchaseOrderNo,latestReport,areaofConcern,source,inspectingCompany,inspectionLocation,viqNo,questions,nextDueDate,shipFlag,shipType,grossTonnage,yearBuilt,classSociety,detention,mouName,deadWeight,company,shipOnWatchlist/Alert,bannedShip,shipRiskDetails,callSign,mmsiNo,underperformingShip,types,certificates,deficiencydNumber,deficiencyCode,actionTaken,sNos,class,extendedDate"; // Build filter string based on whether stage is provided let filterStr; let titleSuffix; if (stage) { filterStr = `imo:${imo} && inspectionType:${inspectionType} && stage:${stage}`; titleSuffix = ` with stage '${stage}'`; } else { filterStr = `imo:${imo} && inspectionType:${inspectionType}`; titleSuffix = ""; } const query = { q: "*", filter_by: filterStr, per_page: perPage, include_fields: includeFields }; // Execute the search let logMsg = `Searching for defects with inspection type '${inspectionType}'`; if (stage) { logMsg += ` and stage '${stage}'`; } logMsg += ` for vessel ${imo}`; logger.info(logMsg); const client = getTypesenseClient(); const results = await client.client.collections(collection).documents().search(query); const hits = results.hits || []; const filteredHits = []; for (const hit of hits) { const document = hit.document || {}; // Remove embedding field to reduce response size if it exists if (typeof document === 'object' && document !== null && 'embedding' in document) { delete document.embedding; } // Convert date fields to human readable format const convertedDoc = convertDefectDates(document); filteredHits.push({ id: convertedDoc.id, score: hit.text_match || 0, document: convertedDoc }); } // Get documents for data link const documents = filteredHits.map(hit => hit.document); // Get data link const dataLink = await getDataLink(documents); // Get vessel name from hits let vesselName = null; try { vesselName = hits.length > 0 ? hits[0].document?.vesselName || null : null; } catch { vesselName = null; } // Insert the data link to mongodb collection let linkHeader = `defects with inspection type ${inspectionType}`; if (stage) { linkHeader += ` and stage ${stage}`; } await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, imo, vesselName || ""); // Format the results const formattedResults = { found: results.found || 0, out_of: results.out_of || 0, page: results.page || 1, hits: filteredHits }; // Convert the results to JSON string const formattedText = JSON.stringify(formattedResults, null, 2); // Create TextContent with all required fields in correct structure const content = { type: "text", text: formattedText, title: `Defects with inspection type '${inspectionType}'${titleSuffix} for vessel ${imo}`, format: "json" }; const artifactData = getArtifact("list_defects_by_inspection_type", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Defects with inspection type '${inspectionType}'${titleSuffix} for vessel ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { const errorMsg = `Error retrieving defects with inspection type '${inspectionType}'`; const fullErrorMsg = stage ? `${errorMsg} and stage '${stage}' for vessel ${imo}` : `${errorMsg} for vessel ${imo}`; logger.error(fullErrorMsg, error); throw error; } } async listDefectsByReportType(arguments_) { logger.info("listDefectsByReportType called", arguments_); const imo = arguments_.imo; const reportType = arguments_.reportType; const stage = arguments_.stage || null; // Default to null for optional parameter const perPage = arguments_.per_page || 250; const sessionId = arguments_.session_id || "testing"; if (!imo || !reportType) { throw new Error("IMO number and report type are required"); } try { const collection = "defect"; const includeFields = "imo,vesselName,referenceNo,currentStatus,closingDate,inspectedBy,inspectionType,reportDate,finding,riskCategory,correctiveAction,isExtended,stage,targetDate,inspectionCurrentStatus,inspectionTargetDate,findingsReference,reportType,areaOfConcern,purchaseRequisitionNo,purchaseOrderNo,latestReport,areaofConcern,source,inspectingCompany,inspectionLocation,viqNo,questions,nextDueDate,shipFlag,shipType,grossTonnage,yearBuilt,classSociety,detention,mouName,deadWeight,company,shipOnWatchlist/Alert,bannedShip,shipRiskDetails,callSign,mmsiNo,underperformingShip,types,certificates,deficiencydNumber,deficiencyCode,actionTaken,sNos,class,extendedDate"; // Build filter string based on whether stage is provided let filterStr; let titleSuffix; if (stage) { filterStr = `imo:${imo} && reportType:${reportType} && stage:${stage}`; titleSuffix = ` with stage '${stage}'`; } else { filterStr = `imo:${imo} && reportType:${reportType}`; titleSuffix = ""; } const query = { q: "*", filter_by: filterStr, per_page: perPage, include_fields: includeFields }; // Execute the search let logMsg = `Searching for defects with report type '${reportType}'`; if (stage) { logMsg += ` and stage '${stage}'`; } logMsg += ` for vessel ${imo}`; logger.info(logMsg); const client = getTypesenseClient(); const results = await client.client.collections(collection).documents().search(query); const hits = results.hits || []; const filteredHits = []; for (const hit of hits) { const document = hit.document || {}; // Remove embedding field to reduce response size if it exists if (typeof document === 'object' && document !== null && 'embedding' in document) { delete document.embedding; } // Convert date fields to human readable format const convertedDoc = convertDefectDates(document); filteredHits.push({ id: convertedDoc.id, score: hit.text_match || 0, document: convertedDoc }); } // Get documents for data link const documents = filteredHits.map(hit => hit.document); // Get data link const dataLink = await getDataLink(documents); // Get vessel name from hits let vesselName = null; try { vesselName = hits.length > 0 ? hits[0].document?.vesselName || null : null; } catch { vesselName = null; } // Insert the data link to mongodb collection let linkHeader = `defects with report type ${reportType}`; if (stage) { linkHeader += ` and stage ${stage}`; } await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, imo, vesselName || ""); // Format the results const formattedResults = { found: results.found || 0, out_of: results.out_of || 0, page: results.page || 1, hits: filteredHits }; // Convert the results to JSON string const formattedText = JSON.stringify(formattedResults, null, 2); // Create TextContent with all required fields in correct structure const content = { type: "text", text: formattedText, title: `Defects with report type '${reportType}'${titleSuffix} for vessel ${imo}`, format: "json" }; const artifactData = getArtifact("list_defects_by_report_type", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Defects with report type '${reportType}'${titleSuffix} for vessel ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { const errorMsg = `Error retrieving defects with report type '${reportType}'`; const fullErrorMsg = stage ? `${errorMsg} and stage '${stage}' for vessel ${imo}` : `${errorMsg} for vessel ${imo}`; logger.error(fullErrorMsg, error); throw error; } } async listDefectsByStatus(arguments_) { logger.info("listDefectsByStatus called", arguments_); const imo = arguments_.imo; const currentStatus = arguments_.currentStatus; const stage = arguments_.stage || null; // Default to null for optional parameter const perPage = arguments_.per_page || 250; const sessionId = arguments_.session_id || "testing"; if (!imo || !currentStatus) { throw new Error("IMO number and current status are required"); } try { const collection = "defect"; const includeFields = "imo,vesselName,referenceNo,currentStatus,closingDate,inspectedBy,inspectionType,reportDate,finding,riskCategory,correctiveAction,isExtended,stage,targetDate,inspectionCurrentStatus,inspectionTargetDate,findingsReference,reportType,areaOfConcern,purchaseRequisitionNo,purchaseOrderNo,latestReport,areaofConcern,source,inspectingCompany,inspectionLocation,viqNo,questions,nextDueDate,shipFlag,shipType,grossTonnage,yearBuilt,classSociety,detention,mouName,deadWeight,company,shipOnWatchlist/Alert,bannedShip,shipRiskDetails,callSign,mmsiNo,underperformingShip,types,certificates,deficiencydNumber,deficiencyCode,actionTaken,sNos,class,extendedDate"; // Build filter string based on whether stage is provided let filterStr; let titleSuffix; if (stage) { filterStr = `imo:${imo} && currentStatus:${currentStatus} && stage:${stage}`; titleSuffix = ` with stage '${stage}'`; } else { filterStr = `imo:${imo} && currentStatus:${currentStatus}`; titleSuffix = ""; } const query = { q: "*", filter_by: filterStr, per_page: perPage, include_fields: includeFields }; // Execute the search let logMsg = `Searching for defects with current status '${currentStatus}'`; if (stage) { logMsg += ` and stage '${stage}'`; } logMsg += ` for vessel ${imo}`; logger.info(logMsg); const client = getTypesenseClient(); const results = await client.client.collections(collection).documents().search(query); const hits = results.hits || []; const filteredHits = []; for (const hit of hits) { const document = hit.document || {}; // Remove embedding field to reduce response size if it exists if (typeof document === 'object' && document !== null && 'embedding' in document) { delete document.embedding; } // Convert date fields to human readable format const convertedDoc = convertDefectDates(document); filteredHits.push({ id: convertedDoc.id, score: hit.text_match || 0, document: convertedDoc }); } // Get documents for data link const documents = filteredHits.map(hit => hit.document); // Get data link const dataLink = await getDataLink(documents); // Get vessel name from hits let vesselName = null; try { vesselName = hits.length > 0 ? hits[0].document?.vesselName || null : null; } catch { vesselName = null; } // Insert the data link to mongodb collection let linkHeader = `defects with status ${currentStatus}`; if (stage) { linkHeader += ` and stage ${stage}`; } await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, imo, vesselName || ""); // Format the results const formattedResults = { found: results.found || 0, out_of: results.out_of || 0, page: results.page || 1, hits: filteredHits }; // Convert the results to JSON string const formattedText = JSON.stringify(formattedResults, null, 2); // Create TextContent with all required fields in correct structure const content = { type: "text", text: formattedText, title: `Defects with current status '${currentStatus}'${titleSuffix} for vessel ${imo}`, format: "json" }; const artifactData = getArtifact("list_defects_by_status", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Defects with current status '${currentStatus}'${titleSuffix} for vessel ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { const errorMsg = `Error retrieving defects with current status '${currentStatus}'`; const fullErrorMsg = stage ? `${errorMsg} and stage '${stage}' for vessel ${imo}` : `${errorMsg} for vessel ${imo}`; logger.error(fullErrorMsg, error); throw error; } } async getAllVesselDefectsRecords(arguments_) { logger.info("getAllVesselDefectsRecords called", arguments_); const collection = "defect"; const sessionId = arguments_.session_id || "testing"; const imo = arguments_.imo; const startDate = arguments_.start_date; const endDate = arguments_.end_date; if (!imo) { throw new Error("IMO is required"); } try { const filterParts = [`imo:=${imo}`]; const dateToTs = (dateStr) => { return Math.floor(new Date(dateStr).getTime() / 1000); }; if (startDate) { const startTs = dateToTs(startDate); filterParts.push(`reportDate:>=${startTs}`); } if (endDate) { const endTs = dateToTs(endDate); filterParts.push(`reportDate:<=${endTs}`); } const filterBy = filterParts.join(" && "); const query = { filter_by: filterBy, exclude_fields: "_id,docId,fleetId,vesselId,fleetManagerId,technicalSuperintendentId,id" }; const client = getTypesenseClient(); const exportResult = await client.client.collections(collection).documents().export(query); let exportData; if (typeof exportResult === 'string') { exportData = exportResult; } else if (exportResult && typeof exportResult === 'object' && 'buffer' in exportResult) { exportData = new TextDecoder().decode(exportResult); } else { exportData = String(exportResult); } const documents = exportData .split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line)); // Convert timestamps to date strings for specified fields const dateFields = [ 'inspectionTargetDate', 'reportDate', 'closingDate', 'targetDate', 'nextDueDate', 'extendedDate' ]; for (const doc of documents) { for (const field of dateFields) { if (field in doc && typeof doc[field] === 'number') { try { doc[field] = new Date(doc[field] * 1000).toISOString().replace('T', ' ').substring(0, 19); } catch (error) { // Keep original value if conversion fails } } } } const vesselName = documents.length > 0 ? documents[0].vesselName : null; const imoExtracted = documents.length > 0 ? documents[0].imo : null; const dataLink = await getDataLink(documents); const linkHeader = `Defect export for IMO ${imo}`; await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, imo, vesselName); const formattedResults = { found: documents.length, out_of: documents.length, page: 1, hits: documents }; const content = { type: "text", text: JSON.stringify(formattedResults, null, 2), title: `Exported defect records for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_all_vessel_defects_records", dataLink); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Defect export artifact for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error executing defect export: ${error}`); throw new Error(`Error exporting defect records: ${String(error)}`); } } // Maritime Compliance Tools async getVesselDetails(arguments_) { logger.info("getVesselDetails called", arguments_); const query = arguments_.query; if (!query) { return [{ type: "text", text: "Error: 'query' parameter is required for vessel details search" }]; } try { logger.info(`Searching for vessel details with vessel name: ${query}`); // Set up search parameters for the fleet-vessel-lookup collection const searchParameters = { q: query, query_by: 'vesselName', per_page: 1, include_fields: 'vesselName,imo,class,flag,shippalmDoc,isV3', prefix: false, num_typos: 2, }; // Execute search const client = getTypesenseClient(); const raw = await client.client.collections('fleet-vessel-lookup').documents().search(searchParameters); const hits = raw.hits || []; if (!hits.length) { return [{ type: "text", text: `No vessels found named '${query}'.` }]; } // Process and format results const doc = hits[0].document || {}; const results = { vesselName: doc.vesselName, imo: doc.imo, class: doc.class, flag: doc.flag, shippalmDoc: doc.shippalmDoc, isV3: doc.isV3, score: hits[0].text_match || 0 }; // Return formatted response const content = { type: "text", text: JSON.stringify(results, null, 2), title: `Vessel details for '${query}'`, format: "json" }; const link = results.link || null; const vesselName = results.vesselName || null; const imo = results.imo || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "vessel details", sessionId, imo, vesselName); const artifactData = getArtifact("get_vessel_details", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Vessel details for '${query}'`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error searching for vessel details: ${error}`); return [{ type: "text", text: `Error querying vessel details: ${String(error)}` }]; } } async getDefectTableSchema(arguments_) { logger.info("getDefectTableSchema called", arguments_); const category = arguments_.category; if (!category) { throw new Error("Category is required"); } try { // Execute the query const collectionName = "typesense_schema"; const query = { category: category }; const projection = { _id: 0, schema: 1, category: 1 }; const mongoClient = getMongoClient(); await mongoClient.connect(); const db = mongoClient.db; const collection = db.collection(collectionName); const cursor = collection.find(query, { projection: projection }); const documents = await cursor.toArray(); await mongoClient.close(); // Format the results const formattedResults = { count: documents.length, documents: documents }; // Convert the results to JSON string const formattedText = JSON.stringify(formattedResults, null, 2); // Create TextContent const content = { type: "text", text: formattedText, title: `Query results from '${collectionName}'`, format: "json" }; return [content]; } catch (error) { logger.error(`Error querying collection typesense_schema: ${error}`); throw new Error(`Error querying collection: ${String(error)}`); } } async getTmsaSummary(arguments_) { logger.info("getTmsaSummary called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "154"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "tmsa summary", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `TMSA Summary for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_tmsa_summary", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `TMSA Summary for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching TMSA summary for IMO ${imo}: ${error}`); throw error; } } async getSireReportsFromOcimf(arguments_) { logger.info("getSireReportsFromOcimf called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "131"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "sire reports from ocimf", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `SIRE Inspection Records from OCIMF for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_sire_reports_from_ocimf", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `SIRE Inspection Records from OCIMF for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching SIRE reports from OCIMF for IMO ${imo}: ${error}`); throw error; } } async fetchFleetHistoricalSireObservations(arguments_) { logger.info("fetchFleetHistoricalSireObservations called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "129"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "fleet historical sire observations", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `Fleet Historical SIRE Observations for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("fetch_fleet_historical_sire_observations", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Fleet Historical SIRE Observations for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching fleet historical SIRE observations for IMO ${imo}: ${error}`); throw error; } } async getCdiReportsFromOcimf(arguments_) { logger.info("getCdiReportsFromOcimf called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "133"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "cdi reports from ocimf", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `CDI Reports from OCIMF for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_cdi_reports_from_ocimf", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `CDI Reports from OCIMF for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching CDI reports from OCIMF for IMO ${imo}: ${error}`); throw error; } } async getVirStatusOverview(arguments_) { logger.info("getVirStatusOverview called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "120"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "vir status overview", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `VIR Status Overview for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_vir_status_overview", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `VIR Status Overview for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching VIR status overview for IMO ${imo}: ${error}`); throw error; } } async getInternalAuditSummary(arguments_) { logger.info("getInternalAuditSummary called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "94"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "internal audit summary", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `ISM Internal Audit Summary for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_internal_audit_summary", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `ISM Internal Audit Summary for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching internal audit summary for IMO ${imo}: ${error}`); throw error; } } async getPscInspectionDefects(arguments_) { logger.info("getPscInspectionDefects called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "46"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "psc inspection defects", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `PSC Inspection Defects for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_psc_inspection_defects", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `PSC Inspection Defects for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching PSC inspection defects for IMO ${imo}: ${error}`); throw error; } } async getSummaryOfDefects(arguments_) { logger.info("getSummaryOfDefects called", arguments_); const imo = arguments_.imo; if (!imo) { throw new Error("IMO is required"); } try { const data = await fetchQaDetails(imo, "55"); // Get link and vessel name for MongoDB const link = data?.link || null; const vesselName = data?.vesselName || null; const sessionId = arguments_.session_id || "testing"; await insertDataLinkToMongodb(link, "summary of all defects", sessionId, imo, vesselName); // Optional: pretty print the dictionary const formattedText = JSON.stringify(data, null, 2); const content = { type: "text", text: formattedText, title: `Summary of All Defects for IMO ${imo}`, format: "json" }; const artifactData = getArtifact("get_summary_of_defects", link); const artifact = { type: "text", text: JSON.stringify(artifactData, null, 2), title: `Summary of All Defects for IMO ${imo}`, format: "json" }; return [content, artifact]; } catch (error) { logger.error(`Error fetching summary of defects for IMO ${imo}: ${error}`); throw error; } } // Search & Document Tools async googleSearch(arguments_) { logger.info("googleSearch called", arguments_); const query = arguments_.query; if (!query) { throw new Error("Search query is required"); } if (!config.perplexityApiKey) { throw new Error("Perplexity API key is not configured"); } const url = "https://api.perplexity.ai/chat/completions"