search_documents
Search project documents by content with filtering by project, document type, and result limits to find specific information efficiently.
Instructions
Search documents by content with advanced filtering and ranking
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query to find in document titles and content | |
| project_id | No | Limit search to specific project | |
| document_types | No | Filter by document types | |
| limit | No | Maximum number of results to return | |
| include_content | No | Whether to include full document content in results |
Implementation Reference
- src/tools/documents.ts:295-331 (handler)Primary execution handler for the search_documents tool. Parses input arguments using Zod, queries Supabase for matching documents based on search term and filters, ranks results by relevance (title match, content similarity, recency), and returns paginated results with metadata.export const searchDocuments = requireAuth(async (args: any) => { const { query, project_id, document_types, limit, include_content } = SearchDocumentsSchema.parse(args) logger.info('Searching documents', { query, project_id, document_types, limit }) // Get all matching documents const allDocuments = await supabaseService.getDocuments( { project_id, search: query }, { limit: limit * 2 }, // Get more for better ranking { field: 'updated_at', order: 'desc' } ) // Filter by document types if specified let filteredDocuments = allDocuments if (document_types && document_types.length > 0) { filteredDocuments = allDocuments.filter(doc => document_types.includes(doc.document_type)) } // Rank results by relevance const rankedResults = rankSearchResults(filteredDocuments, query, include_content) // Limit results const finalResults = rankedResults.slice(0, limit) return { results: finalResults, total_found: rankedResults.length, search_metadata: { query, filters: { project_id, document_types }, ranking_factors: ['title_match', 'content_relevance', 'document_freshness', 'ai_readiness'] } } })
- src/tools/documents.ts:45-51 (schema)Zod schema for input validation used within the search_documents handler to parse and validate tool arguments.const SearchDocumentsSchema = z.object({ query: z.string().min(1), project_id: z.string().uuid().optional(), document_types: z.array(z.string()).optional(), limit: z.number().int().positive().max(50).default(20), include_content: z.boolean().default(false) })
- src/tools/documents.ts:1346-1357 (registration)Registration object mapping tool names to their handler functions for all document-related tools, including 'search_documents' mapped to the searchDocuments handler.export const documentHandlers = { list_documents: listDocuments, create_document: createDocument, get_document: getDocument, update_document: updateDocument, search_documents: searchDocuments, get_document_context: getDocumentContext, add_document_collaborator: addDocumentCollaborator, analyze_document_content: analyzeDocumentContent, get_document_collaboration: getDocumentCollaboration, generate_document_template: generateDocumentTemplate, bulk_document_operations: bulkDocumentOperations
- src/index.ts:143-155 (registration)Central registration in the MCP server where documentHandlers (including search_documents) is spread into the allHandlers object used to dispatch tool calls: this.allHandlers[name](args).this.allHandlers = { ...projectHandlers, ...taskHandlers, ...documentHandlers, ...conversationHandlers, ...contextAggregationHandlers, ...workflowAutomationHandlers, ...intelligentSearchHandlers, ...analyticsInsightsHandlers, ...initiativeHandlers, ...promptToProjectTools.reduce((acc, tool) => ({ ...acc, [tool.name]: tool.handler }), {}), ...debugHandlers, }
- src/tools/documents.ts:516-553 (helper)Key helper function used by the handler to compute relevance scores for search results based on title matching, content keyword frequency, recency, and document type, then sorts and returns ranked list.function rankSearchResults(documents: any[], query: string, includeContent: boolean): any[] { const queryLower = query.toLowerCase() return documents .map(doc => { let score = 0 // Title match (highest weight) if (doc.title.toLowerCase().includes(queryLower)) { score += 50 if (doc.title.toLowerCase().startsWith(queryLower)) score += 25 } // Content relevance const contentLower = doc.content.toLowerCase() const queryMatches = (contentLower.match(new RegExp(queryLower, 'g')) || []).length score += Math.min(30, queryMatches * 5) // Document freshness const daysSinceUpdate = (Date.now() - new Date(doc.updated_at).getTime()) / (1000 * 60 * 60 * 24) if (daysSinceUpdate < 7) score += 10 else if (daysSinceUpdate < 30) score += 5 // AI readiness // Removed metadata check as it doesn't exist in the database schema // Document type relevance if (['readme', 'api_docs'].includes(doc.document_type)) score += 10 return { ...doc, content: includeContent ? doc.content : undefined, search_score: score, query_matches: queryMatches } }) .sort((a, b) => b.search_score - a.search_score) }