UNPKG

@mseep/atlas-mcp-server

Version:

A Model Context Protocol (MCP) server for ATLAS, a Neo4j-powered task management system for LLM Agents - implementing a three-tier architecture (Projects, Tasks, Knowledge) to manage complex workflows.

126 lines (109 loc) 4.76 kB
import { SearchService } from '../../../services/neo4j/searchService.js'; import { BaseErrorCode, McpError } from '../../../types/errors.js'; import { ResponseFormat } from '../../../types/mcp.js'; import { logger } from '../../../utils/logger.js'; import { ToolContext } from '../../../utils/security.js'; import { formatUnifiedSearchResponse } from './responseFormat.js'; // Assuming UnifiedSearchResponse is defined correctly in types.ts import { UnifiedSearchRequestInput, UnifiedSearchRequestSchema, UnifiedSearchResponse } from './types.js'; export const atlasUnifiedSearch = async ( input: unknown, context: ToolContext ): Promise<any> => { const requestId = context.requestContext?.requestId; try { // Parse and validate input against schema const validatedInput = UnifiedSearchRequestSchema.parse(input) as UnifiedSearchRequestInput & { responseFormat?: ResponseFormat }; // Log operation logger.info("Performing unified search", { searchTerm: validatedInput.value, entityTypes: validatedInput.entityTypes, property: validatedInput.property, fuzzy: validatedInput.fuzzy, requestId: requestId }); if (!validatedInput.value || validatedInput.value.trim() === '') { throw new McpError( BaseErrorCode.VALIDATION_ERROR, "Search value cannot be empty", { param: "value" } ); } // --- Simplified Logic: Always use fullTextSearch --- // Construct the Lucene query string let luceneQuery: string; const property = validatedInput.property?.trim(); const isFuzzy = validatedInput.fuzzy === true; // Escape common Lucene special characters, including quotes if we might add them const escapeLucene = (str: string) => str.replace(/([+\-!(){}\[\]^"~*?:\\\/"])/g, '\\$1'); const escapedValue = escapeLucene(validatedInput.value); if (property) { // Search within a specific property if (isFuzzy) { luceneQuery = `${property}:${escapedValue}~1`; // Fuzzy on specific property } else { luceneQuery = `${property}:"${escapedValue}"`; // Exact phrase on specific property } } else { // Search across default indexed fields if (isFuzzy) { luceneQuery = `${escapedValue}~1`; // Fuzzy on default fields } else { // For non-fuzzy, no-property search, just use the escaped value. // Wrapping in quotes here might be too restrictive if searching description/text. luceneQuery = escapedValue; } } logger.info("Using simplified full-text search", { luceneQuery, input: validatedInput, requestId }); // Always call fullTextSearch with the constructed Lucene query const searchResults = await SearchService.fullTextSearch(luceneQuery, { entityTypes: validatedInput.entityTypes, taskType: validatedInput.taskType, page: validatedInput.page, limit: validatedInput.limit }); // Add robust check for searchResults and searchResults.data if (!searchResults || !Array.isArray(searchResults.data)) { logger.error("Search service returned invalid data structure.", { searchResults, requestId }); throw new McpError( BaseErrorCode.INTERNAL_ERROR, "Received invalid data structure from search service." ); } logger.info("Unified search completed successfully", { resultCount: searchResults.data.length, totalResults: searchResults.total, requestId: requestId }); // Create the response object with search results data, mapping 'data' to 'results' const responseData: UnifiedSearchResponse = { results: searchResults.data, // Map data to results total: searchResults.total, page: searchResults.page, limit: searchResults.limit, totalPages: searchResults.totalPages }; // Format and return the response // formatUnifiedSearchResponse expects UnifiedSearchResponse which has 'results' property return formatUnifiedSearchResponse(responseData); } catch (error) { // Log the specific error message and stack if available const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorStack = error instanceof Error ? error.stack : undefined; logger.error("Failed to perform unified search", { error: errorMessage, stack: errorStack, // Log stack trace originalError: error, // Log the original error object requestId: requestId }); // Re-throw as McpError if not already one if (error instanceof McpError) { throw error; } else { throw new McpError( BaseErrorCode.INTERNAL_ERROR, `Error performing unified search: ${errorMessage}` ); } } };