UNPKG

navigation-equipment-manuals-mcp-server

Version:

Navigation Equipment Manuals MCP Server for create, update, delete, and search navigation equipment manuals

235 lines 8.57 kB
import { Client } from 'typesense'; import { logger } from './api.js'; let typesenseClient; /** * Initialize Typesense client */ export function initializeTypesense(config) { try { typesenseClient = new Client({ nodes: [ { host: config.host, port: parseInt(config.port), protocol: config.protocol, }, ], apiKey: config.apiKey, connectionTimeoutSeconds: 10, }); logger.log('Typesense client initialized successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown Typesense initialization error'; logger.error('Failed to initialize Typesense client', error); throw new Error(`Typesense initialization failed: ${errorMessage}`); } } /** * Get the Typesense client instance */ export function getTypesenseClient() { if (!typesenseClient) { throw new Error('Typesense client not initialized. Call initializeTypesense first.'); } return typesenseClient; } /** * Search for navigation equipment manuals using Typesense */ export async function searchByManuals(query, filters, limit = 10) { try { if (!typesenseClient) { throw new Error('Typesense client not initialized'); } const searchParameters = { q: query, query_by: 'equipmentName,content,manufacturer,model,category,manualType', limit: Math.min(limit, 50), highlight_full_fields: 'content', snippet_threshold: 30, num_typos: 2, typo_tokens_threshold: 1, drop_tokens_threshold: 1, highlight_affix_num_tokens: 4, }; // Add filters if provided if (filters) { const filterBy = []; if (filters.category) { filterBy.push(`category:=${filters.category}`); } if (filters.manufacturer) { filterBy.push(`manufacturer:=${filters.manufacturer}`); } if (filters.manualType) { filterBy.push(`manualType:=${filters.manualType}`); } if (filterBy.length > 0) { searchParameters.filter_by = filterBy.join(' && '); } } logger.debug(`Searching Typesense with parameters: ${JSON.stringify(searchParameters)}`); const searchResults = await typesenseClient .collections('navigation_manuals') .documents() .search(searchParameters); logger.debug(`Typesense search returned ${searchResults.hits?.length || 0} results`); return searchResults.hits || []; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown search error'; logger.error('Typesense search failed', error); throw new Error(`Search failed: ${errorMessage}`); } } /** * Index a manual document in Typesense */ export async function indexManual(manual) { try { if (!typesenseClient) { throw new Error('Typesense client not initialized'); } // Prepare document for indexing const document = { id: manual._id?.toString() || manual.id, equipmentName: manual.equipmentName || '', equipmentId: manual.equipmentId?.toString() || '', manufacturer: manual.manufacturer || '', model: manual.model || '', category: manual.category || 'general', manualType: manual.manualType || 'general', version: manual.version || '', status: manual.status || 'active', content: extractContentFromPages(manual.pages || []), pageCount: manual.pages?.length || 0, indexTerms: extractIndexTerms(manual.index || []), createdAt: manual.createdAt || new Date().toISOString(), updatedAt: manual.updatedAt || new Date().toISOString(), }; await typesenseClient .collections('navigation_manuals') .documents() .upsert(document); logger.debug(`Indexed manual: ${manual.equipmentName} (${manual.equipmentId})`); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown indexing error'; logger.error('Failed to index manual in Typesense', error); throw new Error(`Indexing failed: ${errorMessage}`); } } /** * Delete a manual from Typesense index */ export async function deleteManualFromIndex(manualId) { try { if (!typesenseClient) { throw new Error('Typesense client not initialized'); } await typesenseClient .collections('navigation_manuals') .documents(manualId) .delete(); logger.debug(`Deleted manual from index: ${manualId}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown deletion error'; logger.error('Failed to delete manual from Typesense', error); // Don't throw error for deletion failures as they might not exist logger.warn(`Could not delete manual from index: ${errorMessage}`); } } /** * Create or update the Typesense collection schema */ export async function createOrUpdateSchema() { try { if (!typesenseClient) { throw new Error('Typesense client not initialized'); } const schema = { name: 'navigation_manuals', fields: [ { name: 'equipmentName', type: 'string' }, { name: 'equipmentId', type: 'string' }, { name: 'manufacturer', type: 'string', optional: true }, { name: 'model', type: 'string', optional: true }, { name: 'category', type: 'string', facet: true }, { name: 'manualType', type: 'string', facet: true }, { name: 'version', type: 'string', optional: true }, { name: 'status', type: 'string', facet: true }, { name: 'content', type: 'string' }, { name: 'pageCount', type: 'int32' }, { name: 'indexTerms', type: 'string[]', optional: true }, { name: 'createdAt', type: 'string' }, { name: 'updatedAt', type: 'string' }, ], default_sorting_field: 'updatedAt', }; try { // Try to get existing collection await typesenseClient.collections('navigation_manuals').retrieve(); logger.info('Typesense collection already exists'); } catch (error) { // Collection doesn't exist, create it await typesenseClient.collections().create(schema); logger.info('Created Typesense collection: navigation_manuals'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown schema error'; logger.error('Failed to create/update Typesense schema', error); throw new Error(`Schema operation failed: ${errorMessage}`); } } /** * Extract searchable content from manual pages */ function extractContentFromPages(pages) { if (!Array.isArray(pages)) { return ''; } return pages .map(page => { const content = page.content || ''; const title = page.title || ''; const section = page.section || ''; const subsection = page.subsection || ''; return [title, section, subsection, content].filter(Boolean).join(' '); }) .join(' ') .substring(0, 50000); // Limit content length for indexing } /** * Extract index terms for searchability */ function extractIndexTerms(indexEntries) { if (!Array.isArray(indexEntries)) { return []; } return indexEntries .map(entry => entry.term) .filter(Boolean) .filter((term, index, array) => array.indexOf(term) === index) // Remove duplicates .slice(0, 100); // Limit number of terms } /** * Check Typesense connection health */ export async function checkTypesenseHealth() { try { if (!typesenseClient) { return false; } await typesenseClient.health.retrieve(); return true; } catch (error) { logger.error('Typesense health check failed', error); return false; } } //# sourceMappingURL=typesense.js.map