survey-mcp-server
Version:
419 lines (418 loc) • 23.3 kB
JavaScript
import { processTypesenseResults, getTypesenseClient, getVesselImoListFromFleet, fetchQADetailsAndCreateResponse, exportSurveysForImoList, getDataLink, convertToCSV, combined_mongotools_with_grouped_category_mapping } from "./helper_functions.js";
import { updateTypesenseFilterWithCompanyImos, isValidImoForCompany, getAuthorizedImoNumbers, shouldBypassImoFiltering } from "../utils/company-filtering.js";
/**
* ============================================================================
* OLD IMPLEMENTATION (COMMENTED OUT - REPLACED BY OPTIMIZED VERSION)
* ============================================================================
* The old shouldBypassImoFiltering function has been replaced with an optimized
* version from the company-filtering module that uses case-insensitive matching.
*
* See: src/utils/company-filtering.ts for the optimized implementation
* ============================================================================
*/
// /**
// * Check if IMO filtering should be bypassed for admin companies
// */
// function shouldBypassImoFiltering(companyName: string): boolean {
// const adminCompanies = ['admin', 'Admin', 'ADMIN', 'SYIA', 'Syia'];
// return adminCompanies.includes(companyName);
// }
export async function export_fleet_surveys(arguments_) {
// // logger.info("exportFleetSurveys called", arguments_);
const fleetImo = arguments_.fleet_imo;
if (!fleetImo) {
throw new Error(`fleet_imo parameter is required. Only the following property is allowed: fleet_imo. Please check the tool schema for export_fleet_surveys for the allowed properties.`);
}
try {
// Step 1: Get vessel IMO list from fleet
// // logger.info(`Step 1: Getting vessel IMO list for fleet IMO ${fleetImo}`);
let vesselImoList = await getVesselImoListFromFleet(fleetImo);
if (!vesselImoList || vesselImoList.length === 0) {
return { content: [{
type: "text",
text: `No vessels found for fleet IMO: ${fleetImo}`,
title: "No Vessels Found"
}] };
}
// Filter IMO list by company
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const companyImos = await getAuthorizedImoNumbers(companyName, finalDbName, finalMongoUri);
if (companyImos.length > 0) {
const companyImoNumbers = companyImos.map(imo => Number(imo));
vesselImoList = vesselImoList.filter(imo => companyImoNumbers.includes(imo));
}
else {
// If no company IMOs configured, return empty result
return { content: [{
type: "text",
text: `No vessels found for fleet IMO: ${fleetImo} (company filtering applied)`,
title: "No Vessels Found"
}] };
}
}
if (!vesselImoList || vesselImoList.length === 0) {
return { content: [{
type: "text",
text: `No vessels found for fleet IMO: ${fleetImo} (company filtering applied)`,
title: "No Vessels Found"
}] };
}
// // logger.info(`Found ${vesselImoList.length} vessels in fleet IMO ${fleetImo}`);
// Step 2: Export surveys for all vessels in the fleet
// // logger.info(`Step 2: Exporting surveys for ${vesselImoList.length} vessels`);
const surveyDocuments = await exportSurveysForImoList(vesselImoList);
if (!surveyDocuments || surveyDocuments.length === 0) {
return { content: [{
type: "text",
text: `No survey/certificate records found for fleet IMO ${fleetImo} with the specified criteria`,
title: "No Survey Records Found"
}] };
}
// // logger.info(`Found ${surveyDocuments.length} survey/certificate records for fleet IMO ${fleetImo}`);
// Step 3: Convert to CSV
// // logger.info(`Step 3: Converting ${surveyDocuments.length} records to CSV`);
const csvData = convertToCSV(surveyDocuments);
// Insert tracking data to MongoDB (optional)
try {
const dataLink = await getDataLink(surveyDocuments);
const linkHeader = `Fleet surveys export for fleet IMO ${fleetImo}`;
const vesselName = surveyDocuments.length > 0 ? surveyDocuments[0].vesselName : null;
}
catch (trackingError) {
// logger.warn(`Failed to insert tracking data: ${trackingError}`);
// Continue execution even if tracking fails
}
// Return CSV data
const content = {
type: "text",
text: csvData,
title: `Fleet Surveys Export - IMO ${fleetImo}`,
format: "csv"
};
return { content: [content] };
}
catch (error) {
// logger.error(`Error exporting fleet surveys for IMO ${fleetImo}: ${error}`);
throw new Error(`Error exporting fleet surveys: ${String(error)}`);
}
}
export async function get_fleet_dry_docking_status(arguments_) {
const imo = arguments_.imo;
if (!imo) {
throw new Error(`imo parameter is required. Only the following property is allowed: imo. Please check the tool schema for get_fleet_dry_docking_status for the allowed properties.`);
}
// Validate IMO for company
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const isValid = await isValidImoForCompany(imo, companyName, finalDbName, finalMongoUri);
if (!isValid) {
return { content: [{
type: "text",
text: `Access denied: IMO ${imo} is not authorized for company ${companyName}`,
title: "Access Denied"
}] };
}
}
// Debug logging
// console.log(`getFleetDryDockingStatus called with IMO: ${imo}`);
// console.log(`process.env.VESSEL_INFO_DB_NAME: ${process.env.VESSEL_INFO_DB_NAME}`);
// console.log(`process.env.VESSEL_INFO_MONGO_URI: ${process.env.VESSEL_INFO_MONGO_URI ? 'Set' : 'Not set'}`);
try {
const result = await fetchQADetailsAndCreateResponse(imo.toString(), 26, "get_fleet_dry_docking_status", "fleet dry docking status", process.env.VESSEL_INFO_DB_NAME, process.env.VESSEL_INFO_MONGO_URI);
return { content: result };
}
catch (error) {
// logger.error(`Error getting fleet dry docking status for IMO ${imo}:`, error);
throw new Error(`Error getting fleet dry docking status: ${error.message}`);
}
}
export async function get_fleet_annual_survey_status(arguments_) {
const imo = arguments_.imo;
if (!imo) {
throw new Error(`imo parameter is required. Only the following property is allowed: imo. Please check the tool schema for get_fleet_annual_survey_status for the allowed properties.`);
}
// Validate IMO for company
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const isValid = await isValidImoForCompany(imo, companyName, finalDbName, finalMongoUri);
if (!isValid) {
return { content: [{
type: "text",
text: `Access denied: IMO ${imo} is not authorized for company ${companyName}`,
title: "Access Denied"
}] };
}
}
try {
const result = await fetchQADetailsAndCreateResponse(imo.toString(), 27, "get_fleet_annual_survey_status", "fleet annual survey status", process.env.VESSEL_INFO_DB_NAME, process.env.VESSEL_INFO_MONGO_URI);
return { content: result };
}
catch (error) {
// logger.error(`Error getting fleet annual survey status for IMO ${imo}:`, error);
throw new Error(`Error getting fleet annual survey status: ${error.message}`);
}
}
export async function get_fleet_ihm_certificate_status(arguments_) {
const imo = arguments_.imo;
if (!imo) {
throw new Error(`imo parameter is required. Only the following property is allowed: imo. Please check the tool schema for get_fleet_ihm_certificate_status for the allowed properties.`);
}
// Validate IMO for company
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const isValid = await isValidImoForCompany(imo, companyName, finalDbName, finalMongoUri);
if (!isValid) {
return { content: [{
type: "text",
text: `Access denied: IMO ${imo} is not authorized for company ${companyName}`,
title: "Access Denied"
}] };
}
}
try {
const result = await fetchQADetailsAndCreateResponse(imo.toString(), 91, "get_fleet_ihm_certificate_status", "fleet IHM certificate status", process.env.VESSEL_INFO_DB_NAME, process.env.VESSEL_INFO_MONGO_URI);
return { content: result };
}
catch (error) {
// logger.error(`Error getting fleet IHM certificate status for IMO ${imo}:`, error);
throw new Error(`Error getting fleet IHM certificate status: ${error.message}`);
}
}
export async function get_fleet_lsa_ffa_certificate_status(arguments_) {
const imo = arguments_.imo;
if (!imo) {
throw new Error(`imo parameter is required. Only the following property is allowed: imo. Please check the tool schema for get_fleet_lsa_ffa_certificate_status for the allowed properties.`);
}
// Validate IMO for company
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const isValid = await isValidImoForCompany(imo, companyName, finalDbName, finalMongoUri);
if (!isValid) {
return { content: [{
type: "text",
text: `Access denied: IMO ${imo} is not authorized for company ${companyName}`,
title: "Access Denied"
}] };
}
}
try {
const result = await fetchQADetailsAndCreateResponse(imo.toString(), 109, "get_fleet_lsa_ffa_certificate_status", "fleet LSA/FFA certificate status", process.env.VESSEL_INFO_DB_NAME, process.env.VESSEL_INFO_MONGO_URI);
return { content: result };
}
catch (error) {
// logger.error(`Error getting fleet LSA/FFA certificate status for IMO ${imo}:`, error);
throw new Error(`Error getting fleet LSA/FFA certificate status: ${error.message}`);
}
}
export async function get_vessel_survey_certificate_information(args) {
// logger.info("getVesselSurveyCertificateInformation called", args);
try {
// Validate IMO for company if IMO is provided
if (args.imo) {
const companyName = process.env.COMPANY_NAME || '';
const finalDbName = process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || '';
const finalMongoUri = process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || '';
if (companyName && finalDbName && finalMongoUri && companyName !== "Synergy" && !shouldBypassImoFiltering(companyName)) {
const isValid = await isValidImoForCompany(args.imo, companyName, finalDbName, finalMongoUri);
if (!isValid) {
return { content: [{
type: "text",
text: `Access denied: IMO ${args.imo} is not authorized for company ${companyName}`,
title: "Access Denied"
}] };
}
}
}
// Initialize database connection
const qaDbName = process.env.VESSEL_INFO_DB_NAME || '';
const qaMongoUri = process.env.VESSEL_INFO_MONGO_URI || '';
// Use the combined helper function for grouped category mapping
const result = await combined_mongotools_with_grouped_category_mapping(args, [
"class_society_compliance",
"hazardous_materials_management",
"safety_equipment_compliance",
"erp_certificate_tracking"
], {
"class_society_compliance": [19, 20, 21, 22, 23, 24, 25],
"hazardous_materials_management": [87, 88, 89, 90],
"safety_equipment_compliance": [106, 107, 108],
"erp_certificate_tracking": [115]
}, "get_vessel_survey_certificate_information", qaDbName, qaMongoUri);
return result;
}
catch (error) {
// logger.error("Error in getVesselSurveyCertificateInformation", { error });
const errorResponse = {
type: "text",
text: `Error retrieving vessel survey and certificate information: ${error instanceof Error ? error.message : String(error)}`
};
return { content: [errorResponse] };
}
}
export async function universal_certificate_survey_search(arguments_) {
const collection = "certificate";
// Extract new schema parameters
const args = arguments_;
const query_by = args.query_by;
const query_text = (args.q || "").trim() || "*";
// Validate that query_by is provided when q is specified
if (args.q && args.q.trim() !== "*" && !query_by) {
throw new Error(`query_by parameter is required. Only the following properties are allowed: query_by, q, filter_by, sort_by, page, per_page. Please check the tool schema for universal_certificate_survey_search for the allowed properties.`);
}
const filter_by = args.filter_by || "";
const sort_by = args.sort_by || "relevance";
const page = args.page || 1;
const per_page = args.per_page || 50;
try {
let filterBy = filter_by;
// Apply company filtering
filterBy = await updateTypesenseFilterWithCompanyImos(filterBy || "");
// Convert date fields from yyyy-mm-dd to Unix timestamps in filter_by
if (filterBy) {
const dateFields = ["issueDate", "expiryDate", "windowStartDate", "windowEndDate", "extensionDate"];
for (const dateField of dateFields) {
// Handle date range operations (>=, <=, >, <, =)
const dateRegexPatterns = [
{ pattern: new RegExp(`${dateField}:>=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>=' },
{ pattern: new RegExp(`${dateField}:<=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<=' },
{ pattern: new RegExp(`${dateField}:>(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>' },
{ pattern: new RegExp(`${dateField}:<(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<' },
{ pattern: new RegExp(`${dateField}:(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':' }
];
for (const { pattern, operator } of dateRegexPatterns) {
filterBy = filterBy.replace(pattern, (match, dateStr) => {
try {
const timestamp = Math.floor(new Date(dateStr + 'T00:00:00.000Z').getTime() / 1000);
return `${dateField}${operator}${timestamp}`;
}
catch (error) {
// logger.warn(`Failed to convert date ${dateStr} for field ${dateField}:`, error);
return match; // Keep original if conversion fails
}
});
}
}
}
// Set up all available fields for inclusion (based on schema)
const includeFields = "imo,vesselId,vesselName,certificateSurveyEquipmentName,isExtended,issuingAuthority," +
"currentStatus,dataSource,type,issueDate,expiryDate,windowStartDate,windowEndDate," +
"extensionDate,daysToExpiry,certificateNumber,certificateLink";
const excludeFields = "embedding";
const query = {
q: query_text,
query_by: query_by,
include_fields: includeFields,
exclude_fields: excludeFields,
page: page,
per_page: per_page
};
if (filterBy) {
query.filter_by = filterBy;
}
// Handle sorting
if (sort_by && sort_by !== "relevance") {
query.sort_by = sort_by;
}
// logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
let results;
try {
const typesenseClient = await getTypesenseClient();
results = await typesenseClient.collections(collection).documents().search(query);
}
catch (searchError) {
// Check if this is a filter field error using regex
const filterErrorRegex = /Could not find a filter field named `([^`]+)` in the schema/i;
const filterMatch = searchError.message?.match(filterErrorRegex);
if (filterMatch) {
const problematicField = filterMatch[1];
// logger.error(`Filter field '${problematicField}' not found in schema.`);
// Return user-friendly error message
return { content: [{
type: "text",
text: `Error: The field '${problematicField}' is not available for filtering in the certificate/survey schema.\n\nPlease remove '${problematicField}' from your filter_by parameter and try again.`,
title: "Invalid Filter Field",
format: "text"
}] };
}
// Check if this is a sorting error using regex
const sortErrorRegex = /Could not find a field named `([^`]+)` in the schema for sorting/i;
const sortMatch = searchError.message?.match(sortErrorRegex);
if (sortMatch && query.sort_by) {
const problematicField = sortMatch[1];
// logger.warn(`Sorting field '${problematicField}' not found in schema. Removing from sort_by and retrying...`);
// Remove the problematic field from sort_by
const sortByFields = query.sort_by.split(',').map((field) => field.trim());
const filteredSortBy = sortByFields.filter((field) => {
// Remove both "fieldname:asc" and "fieldname:desc" patterns
const fieldName = field.split(':')[0];
return fieldName !== problematicField;
});
// If there are remaining fields, use them; otherwise remove sort_by entirely
if (filteredSortBy.length > 0) {
query.sort_by = filteredSortBy.join(',');
// logger.debug(`[Typesense Query Retry] ${JSON.stringify(query)}`);
}
else {
delete query.sort_by;
// logger.debug(`[Typesense Query Retry] No valid sort fields remaining, removing sort_by: ${JSON.stringify(query)}`);
}
// Retry the search
try {
const typesenseClient = await getTypesenseClient();
results = await typesenseClient.collections(collection).documents().search(query);
// logger.info(`Search succeeded after removing problematic sort field '${problematicField}'`);
}
catch (retryError) {
// logger.error('Error performing search even after removing problematic sort field:', retryError);
throw retryError;
}
}
else {
// Not a sorting or filter error, rethrow
throw searchError;
}
}
// Debug: Log the structure of the search results
// logger.info(`UniversalCertificateSurveySearchHandler - Results structure: ${JSON.stringify(Object.keys(results))}`);
if (results.hits && results.hits.length > 0) {
const sampleHit = results.hits[0];
// logger.info(`UniversalCertificateSurveySearchHandler - Sample hit structure: ${JSON.stringify(Object.keys(sampleHit))}`);
}
if (!results || !results.hits || results.hits.length === 0) {
// throw new Error(`No certificate/survey records found`);
// return a message that no records were found
return { content: [{
type: "text",
text: "No certificate/survey records found",
title: "No certificate/survey records found",
format: "json"
}] };
}
// Format results using the utility function
const title = `Universal Certificate/Survey Search Results for '${query_text}'`;
const linkHeader = `Universal certificate/survey search result for query: '${query_text}' in fields: ${query_by}`;
return await processTypesenseResults(results, "universal_certificate_survey_search", title, linkHeader);
}
catch (error) {
// logger.error('Error performing universal certificate/survey search:', error);
throw new Error(`Error performing search: ${error.message}`);
}
}
// Simple logger
const logger = {
info: (...args) => console.error(...args),
error: (...args) => console.error(...args),
warn: (...args) => console.error(...args),
debug: (...args) => console.error(...args)
};