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
JavaScript
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"