defect-inspection-tools-mcp-server
Version:
Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control
408 lines • 17 kB
JavaScript
import { logger } from './logger.js';
import { SanitizationMiddleware } from '../middleware/sanitization-middleware.js';
export class TypesenseService {
constructor(typesenseService) {
this.typesenseService = typesenseService;
}
// Search across maritime collections
async searchMaritimeData(query, collections = TypesenseService.MARITIME_COLLECTIONS, params = {}) {
try {
const sanitizedQuery = SanitizationMiddleware.sanitizeSearchQuery(query);
const searchParams = {
...TypesenseService.DEFAULT_SEARCH_PARAMS,
...params,
q: sanitizedQuery
};
logger.info('Starting Typesense maritime search', {
query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery),
collections,
params: searchParams
});
// Multi-collection search
const results = [];
for (const collection of collections) {
try {
const collectionResult = await this.searchCollection(collection, searchParams);
if (collectionResult.found > 0) {
results.push(collectionResult);
}
}
catch (error) {
logger.warn(`Search failed for collection ${collection}`, {
collection,
error: error.message
});
}
}
// Combine results
const combinedResult = this.combineSearchResults(results);
logger.info('Typesense maritime search completed', {
query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery),
totalFound: combinedResult.found,
searchTime: combinedResult.search_time_ms,
collectionsSearched: collections.length,
resultsReturned: combinedResult.hits.length
});
return combinedResult;
}
catch (error) {
logger.error('Typesense maritime search failed', {
query: SanitizationMiddleware.sanitizeForLogging(query),
error: error.message
});
throw error;
}
}
// Search specific collection
async searchCollection(collection, params) {
try {
const sanitizedCollection = SanitizationMiddleware.sanitizeString(collection, 100);
const sanitizedParams = this.sanitizeSearchParams(params);
const response = await this.typesenseService.get(`/collections/${sanitizedCollection}/documents/search`, sanitizedParams);
if (!response.success) {
throw new Error(`Typesense search failed: ${response.error}`);
}
const result = response.data?.data;
// Sanitize search result
const sanitizedResult = SanitizationMiddleware.sanitizeToolResponse(result);
return sanitizedResult;
}
catch (error) {
logger.error('Typesense collection search failed', {
collection,
error: error.message
});
throw error;
}
}
// Search defects with maritime-specific filters
async searchDefects(query, filters = {}, params = {}) {
try {
const sanitizedQuery = SanitizationMiddleware.sanitizeSearchQuery(query);
const sanitizedFilters = SanitizationMiddleware.sanitizeToolArguments(filters);
// Build filter string
const filterParts = [];
if (sanitizedFilters.vessel_name) {
filterParts.push(`vessel_name:${sanitizedFilters.vessel_name}`);
}
if (sanitizedFilters.imo_number) {
filterParts.push(`imo_number:${sanitizedFilters.imo_number}`);
}
if (sanitizedFilters.defect_type) {
filterParts.push(`defect_type:${sanitizedFilters.defect_type}`);
}
if (sanitizedFilters.severity) {
filterParts.push(`severity:${sanitizedFilters.severity}`);
}
if (sanitizedFilters.status) {
filterParts.push(`status:${sanitizedFilters.status}`);
}
if (sanitizedFilters.inspection_date_range) {
filterParts.push(`inspection_date:${sanitizedFilters.inspection_date_range}`);
}
if (sanitizedFilters.stage) {
filterParts.push(`stage:${sanitizedFilters.stage}`);
}
const searchParams = {
...TypesenseService.DEFAULT_SEARCH_PARAMS,
...params,
q: sanitizedQuery,
query_by: 'defect_type,description,location,vessel_name,corrective_action',
filter_by: filterParts.join(' && '),
facet_by: 'defect_type,severity,status,stage,vessel_name',
sort_by: 'inspection_date:desc,severity:desc',
highlight_fields: 'description,corrective_action'
};
return await this.searchCollection('defect', searchParams);
}
catch (error) {
logger.error('Defect search failed', {
query: SanitizationMiddleware.sanitizeForLogging(query),
filters: SanitizationMiddleware.sanitizeForLogging(filters),
error: error.message
});
throw error;
}
}
// Search vessels with maritime-specific filters
async searchVessels(query, filters = {}, params = {}) {
try {
const sanitizedQuery = SanitizationMiddleware.sanitizeSearchQuery(query);
const sanitizedFilters = SanitizationMiddleware.sanitizeToolArguments(filters);
// Build filter string
const filterParts = [];
if (sanitizedFilters.imo_number) {
filterParts.push(`imo_number:${sanitizedFilters.imo_number}`);
}
if (sanitizedFilters.vessel_type) {
filterParts.push(`vessel_type:${sanitizedFilters.vessel_type}`);
}
if (sanitizedFilters.flag_state) {
filterParts.push(`flag_state:${sanitizedFilters.flag_state}`);
}
if (sanitizedFilters.class_society) {
filterParts.push(`class_society:${sanitizedFilters.class_society}`);
}
if (sanitizedFilters.built_year_range) {
filterParts.push(`built_year:${sanitizedFilters.built_year_range}`);
}
if (sanitizedFilters.deadweight_range) {
filterParts.push(`deadweight:${sanitizedFilters.deadweight_range}`);
}
const searchParams = {
...TypesenseService.DEFAULT_SEARCH_PARAMS,
...params,
q: sanitizedQuery,
query_by: 'vessel_name,imo_number,vessel_type,flag_state,class_society,owner,manager',
filter_by: filterParts.join(' && '),
facet_by: 'vessel_type,flag_state,class_society,built_year',
sort_by: 'vessel_name:asc',
highlight_fields: 'vessel_name,owner,manager'
};
return await this.searchCollection('vessel', searchParams);
}
catch (error) {
logger.error('Vessel search failed', {
query: SanitizationMiddleware.sanitizeForLogging(query),
filters: SanitizationMiddleware.sanitizeForLogging(filters),
error: error.message
});
throw error;
}
}
// Search inspections with maritime-specific filters
async searchInspections(query, filters = {}, params = {}) {
try {
const sanitizedQuery = SanitizationMiddleware.sanitizeSearchQuery(query);
const sanitizedFilters = SanitizationMiddleware.sanitizeToolArguments(filters);
// Build filter string
const filterParts = [];
if (sanitizedFilters.vessel_name) {
filterParts.push(`vessel_name:${sanitizedFilters.vessel_name}`);
}
if (sanitizedFilters.imo_number) {
filterParts.push(`imo_number:${sanitizedFilters.imo_number}`);
}
if (sanitizedFilters.inspection_type) {
filterParts.push(`inspection_type:${sanitizedFilters.inspection_type}`);
}
if (sanitizedFilters.inspector) {
filterParts.push(`inspector:${sanitizedFilters.inspector}`);
}
if (sanitizedFilters.port) {
filterParts.push(`port:${sanitizedFilters.port}`);
}
if (sanitizedFilters.date_range) {
filterParts.push(`inspection_date:${sanitizedFilters.date_range}`);
}
if (sanitizedFilters.status) {
filterParts.push(`status:${sanitizedFilters.status}`);
}
const searchParams = {
...TypesenseService.DEFAULT_SEARCH_PARAMS,
...params,
q: sanitizedQuery,
query_by: 'inspection_type,inspector,port,vessel_name,findings,recommendations',
filter_by: filterParts.join(' && '),
facet_by: 'inspection_type,inspector,port,status',
sort_by: 'inspection_date:desc',
highlight_fields: 'findings,recommendations'
};
return await this.searchCollection('inspection', searchParams);
}
catch (error) {
logger.error('Inspection search failed', {
query: SanitizationMiddleware.sanitizeForLogging(query),
filters: SanitizationMiddleware.sanitizeForLogging(filters),
error: error.message
});
throw error;
}
}
// Get collection information
async getCollectionInfo(collection) {
try {
const sanitizedCollection = SanitizationMiddleware.sanitizeString(collection, 100);
const response = await this.typesenseService.get(`/collections/${sanitizedCollection}`);
if (!response.success) {
throw new Error(`Failed to get collection info: ${response.error}`);
}
const collectionInfo = response.data?.data;
logger.info('Collection info retrieved', {
collection: sanitizedCollection,
numDocuments: collectionInfo.num_documents,
fieldsCount: collectionInfo.fields.length
});
return collectionInfo;
}
catch (error) {
logger.error('Failed to get collection info', {
collection,
error: error.message
});
throw error;
}
}
// List all collections
async listCollections() {
try {
const response = await this.typesenseService.get('/collections');
if (!response.success) {
throw new Error(`Failed to list collections: ${response.error}`);
}
const collections = response.data?.data;
logger.info('Collections listed', {
collectionsCount: collections.length,
collections: collections.map(c => c.name)
});
return collections;
}
catch (error) {
logger.error('Failed to list collections', {
error: error.message
});
throw error;
}
}
// Upsert document
async upsertDocument(collection, document) {
try {
const sanitizedCollection = SanitizationMiddleware.sanitizeString(collection, 100);
const sanitizedDocument = SanitizationMiddleware.sanitizeToolArguments(document);
const response = await this.typesenseService.post(`/collections/${sanitizedCollection}/documents`, sanitizedDocument, {
params: { action: sanitizedDocument.action || 'upsert' }
});
if (!response.success) {
throw new Error(`Failed to upsert document: ${response.error}`);
}
logger.info('Document upserted successfully', {
collection: sanitizedCollection,
documentId: sanitizedDocument.id,
action: sanitizedDocument.action || 'upsert'
});
return response.data?.data;
}
catch (error) {
logger.error('Failed to upsert document', {
collection,
error: error.message
});
throw error;
}
}
// Delete document
async deleteDocument(collection, documentId) {
try {
const sanitizedCollection = SanitizationMiddleware.sanitizeString(collection, 100);
const sanitizedDocumentId = SanitizationMiddleware.sanitizeString(documentId, 100);
const response = await this.typesenseService.delete(`/collections/${sanitizedCollection}/documents/${sanitizedDocumentId}`);
if (!response.success) {
throw new Error(`Failed to delete document: ${response.error}`);
}
logger.info('Document deleted successfully', {
collection: sanitizedCollection,
documentId: sanitizedDocumentId
});
return response.data?.data;
}
catch (error) {
logger.error('Failed to delete document', {
collection,
documentId,
error: error.message
});
throw error;
}
}
// Private helper methods
sanitizeSearchParams(params) {
const sanitized = { ...params };
// Sanitize string parameters
if (sanitized.q) {
sanitized.q = SanitizationMiddleware.sanitizeSearchQuery(sanitized.q);
}
if (sanitized.query_by) {
sanitized.query_by = SanitizationMiddleware.sanitizeString(sanitized.query_by, 500);
}
if (sanitized.filter_by) {
sanitized.filter_by = SanitizationMiddleware.sanitizeString(sanitized.filter_by, 1000);
}
if (sanitized.sort_by) {
sanitized.sort_by = SanitizationMiddleware.sanitizeString(sanitized.sort_by, 200);
}
if (sanitized.facet_by) {
sanitized.facet_by = SanitizationMiddleware.sanitizeString(sanitized.facet_by, 200);
}
if (sanitized.include_fields) {
sanitized.include_fields = SanitizationMiddleware.sanitizeString(sanitized.include_fields, 200);
}
if (sanitized.exclude_fields) {
sanitized.exclude_fields = SanitizationMiddleware.sanitizeString(sanitized.exclude_fields, 200);
}
// Validate numeric parameters
if (sanitized.page && sanitized.page < 1) {
sanitized.page = 1;
}
if (sanitized.per_page && sanitized.per_page > 250) {
sanitized.per_page = 250;
}
if (sanitized.max_facet_values && sanitized.max_facet_values > 100) {
sanitized.max_facet_values = 100;
}
return sanitized;
}
combineSearchResults(results) {
if (results.length === 0) {
return {
found: 0,
out_of: 0,
page: 1,
request_params: {},
search_time_ms: 0,
hits: []
};
}
if (results.length === 1) {
return results[0];
}
// Combine results from multiple collections
const combined = {
found: results.reduce((sum, r) => sum + r.found, 0),
out_of: results.reduce((sum, r) => sum + r.out_of, 0),
page: results[0].page,
request_params: results[0].request_params,
search_time_ms: Math.max(...results.map(r => r.search_time_ms)),
hits: []
};
// Merge hits and sort by relevance
for (const result of results) {
combined.hits.push(...result.hits);
}
// Sort by text match score
combined.hits.sort((a, b) => (b.text_match || 0) - (a.text_match || 0));
// Limit to reasonable number of results
const maxResults = combined.request_params.per_page || 10;
combined.hits = combined.hits.slice(0, maxResults);
return combined;
}
}
TypesenseService.DEFAULT_SEARCH_PARAMS = {
page: 1,
per_page: 10,
num_typos: '2',
prefix: true,
drop_tokens_threshold: 1,
typo_tokens_threshold: 1,
snippet_threshold: 30,
highlight_affix_num_tokens: 4,
max_facet_values: 10
};
TypesenseService.MARITIME_COLLECTIONS = [
'defect',
'vessel',
'inspection',
'compliance',
'audit',
'report'
];
//# sourceMappingURL=typesense.js.map