Skip to main content
Glama
searchDocs.ts6.19 kB
/** * Search Fabric Documentation Tool Handler * Full implementation using intelligent multi-strategy search with query expansion and relevance scoring */ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { SearchService } from '../services/search-service.js'; import type { SearchResult, SearchOptions } from '../services/search-service.js'; export interface SearchDocsParams { query: string; category?: string; loader?: string; minecraftVersion?: string; includeCode?: boolean; limit?: number; } // Singleton instance for reuse (better performance) let searchServiceInstance: SearchService | null = null; function getSearchService(): SearchService { if (!searchServiceInstance) { searchServiceInstance = new SearchService(); } return searchServiceInstance; } /** * Handle the search_fabric_docs tool call * Performs intelligent multi-strategy search with: * - FTS5 full-text search with fallback to LIKE patterns * - Query tokenization and synonym expansion * - Relevance scoring with weighted factors * - Result deduplication and ranking * - AI-friendly formatted output */ export async function handleSearchDocs(params: SearchDocsParams): Promise<CallToolResult> { const { query, category, loader, minecraftVersion, includeCode = true, limit = 10 } = params; // Validate required parameters if (!query || typeof query !== 'string' || query.trim().length === 0) { return { content: [ { type: 'text', text: 'Error: Query parameter is required and cannot be empty.', }, ], isError: true, }; } const trimmedQuery = query.trim(); // Validate limit const effectiveLimit = Math.min(Math.max(1, limit), 20); try { const searchService = getSearchService(); // Build search options const searchOptions: SearchOptions = { query: trimmedQuery, limit: effectiveLimit, includeCode, }; // Add optional filters if (category && category !== 'all') { searchOptions.category = category; } if (loader) { searchOptions.loader = loader; } if (minecraftVersion) { // Handle 'latest' version if (minecraftVersion.toLowerCase() === 'latest') { const stats = searchService.getStats(); const versions = stats.versions.sort((a, b) => { const partsA = a.split('.').map(Number); const partsB = b.split('.').map(Number); for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { const numA = partsA[i] || 0; const numB = partsB[i] || 0; if (numA !== numB) return numB - numA; } return 0; }); if (versions[0]) { searchOptions.minecraftVersion = versions[0]; } } else { searchOptions.minecraftVersion = minecraftVersion; } } // Perform search const results = await searchService.search(searchOptions); // Format results for AI const formattedOutput = searchService.formatForAI(results, trimmedQuery); // Add metadata for AI to use const metadata = buildMetadata(results, searchOptions, searchService); return { content: [ { type: 'text', text: formattedOutput + '\n\n' + metadata, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`[searchDocs] Error: ${errorMessage}`); return { content: [ { type: 'text', text: `Error searching documentation: ${errorMessage}\n\nPlease ensure the documentation database has been indexed. Run 'npm run index' to build the database.`, }, ], isError: true, }; } } /** * Build metadata section for AI context */ function buildMetadata( results: SearchResult[], options: SearchOptions, service: SearchService ): string { const stats = service.getStats(); let metadata = '---\n**Search Metadata:**\n'; metadata += `- Query: "${options.query}"\n`; metadata += `- Results found: ${results.length}\n`; if (options.category) { metadata += `- Category filter: ${options.category}\n`; } if (options.loader) { metadata += `- Loader filter: ${options.loader}\n`; } if (options.minecraftVersion) { metadata += `- Minecraft version filter: ${options.minecraftVersion}\n`; } metadata += `\n**Database Stats:**\n`; metadata += `- Total documents indexed: ${stats.totalDocuments}\n`; metadata += `- Total sections: ${stats.totalSections}\n`; metadata += `- Available loaders: ${stats.loaders.join(', ')}\n`; metadata += `- Available versions: ${stats.versions.slice(0, 5).join(', ')}${stats.versions.length > 5 ? '...' : ''}\n`; // Add suggestions based on results if (results.length === 0) { metadata += '\n**Suggestions:**\n'; metadata += '- Try broader search terms\n'; metadata += '- Remove category/loader filters\n'; metadata += '- Use synonyms (e.g., "register" instead of "create")\n'; } else if (results.length < 3) { metadata += '\n**Note:** Few results found. Consider:\n'; metadata += '- Using `get_example` for specific code examples\n'; metadata += '- Trying related terms or concepts\n'; } return metadata; } /** * Search documentation (legacy function for compatibility) * @deprecated Use handleSearchDocs instead */ export async function searchDocs(params: SearchDocsParams): Promise<SearchResult[]> { const searchService = getSearchService(); return await searchService.search({ query: params.query, category: params.category, minecraftVersion: params.minecraftVersion, limit: 10, }); } /** * Get available categories for filtering */ export function getAvailableCategories(): string[] { return [ 'getting-started', 'items', 'blocks', 'entities', 'rendering', 'networking', 'data-generation', 'commands', 'sounds', 'events', 'mixins', ]; } /** * Get available loaders for filtering */ export function getAvailableLoaders(): string[] { return ['fabric', 'neoforge', 'shared']; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OGMatrix/mcmodding-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server