search_conversations
Search Cursor chat messages for specific terms, error messages, or code patterns using exact text matching. Optimized for technical queries like "useState" or "async/await". Avoid project-specific searches.
Instructions
Searches through Cursor chat content using exact text matching (NOT semantic search) to find relevant discussions. WARNING: For project-specific searches, use list_conversations with projectPath instead of this tool! This tool is for searching message content, not project filtering.
WHEN TO USE THIS TOOL:
Searching for specific technical terms in message content (e.g., "useState", "async/await")
Finding conversations mentioning specific error messages
Searching for code patterns or function names
WHEN NOT TO USE THIS TOOL:
❌ DON'T use query="project-name" - use list_conversations with projectPath instead
❌ DON'T search for project names in message content
❌ DON'T use this for project-specific filtering
Search methods (all use exact/literal text matching):
Simple text matching: Use query parameter for literal string matching (e.g., "react hooks")
Multi-keyword: Use keywords array with keywordOperator for exact matching
LIKE patterns: Advanced pattern matching with SQL wildcards (% = any chars, _ = single char)
Date range: Filter by message timestamps (YYYY-MM-DD format)
IMPORTANT: When using date filters, call get_system_info first to know today's date.
Examples: likePattern="%useState(%" for function calls, keywords=["typescript","interface"] with AND operator.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| endDate | No | End date for search (YYYY-MM-DD). Note: Timestamps may be unreliable. | |
| includeCode | No | Include code blocks in search results | |
| keywordOperator | No | How to combine keywords: "AND" = all keywords must be present, "OR" = any keyword can be present | OR |
| keywords | No | Array of keywords for exact text matching - use with keywordOperator to find conversations with specific combinations | |
| likePattern | No | SQL LIKE pattern for advanced searches - use % for any characters, _ for single character. Examples: "%useState(%" for function calls, "%.tsx%" for file types | |
| maxResults | No | Maximum number of conversations to return | |
| outputMode | No | Output format: "json" for formatted JSON (default), "compact-json" for minified JSON | json |
| query | No | Exact text matching - searches for literal string occurrences in MESSAGE CONTENT (e.g., "react hooks", "useState", "error message"). ❌ DON'T use for project names - use list_conversations with projectPath instead! | |
| searchType | No | Focus search on specific content types. Use "project" for project-specific searches that leverage file path context. | all |
| startDate | No | Start date for search (YYYY-MM-DD). Note: Timestamps may be unreliable. |
Implementation Reference
- src/tools/conversation-tools.ts:570-855 (handler)Core handler function implementing the search_conversations tool logic. Handles input validation, database connection, various search modes (query, keywords, LIKE patterns, project search, date filtering), relevance scoring, and returns formatted search results.export async function searchConversations(input: SearchConversationsInput): Promise<SearchConversationsOutput> { const validatedInput = searchConversationsSchema.parse(input); const dbPath = process.env.CURSOR_DB_PATH || detectCursorDatabasePath(); const reader = new CursorDatabaseReader({ dbPath }); try { await reader.connect(); // Determine the search query for display purposes const displayQuery = validatedInput.query || (validatedInput.keywords ? validatedInput.keywords.join(` ${validatedInput.keywordOperator} `) : '') || validatedInput.likePattern || 'advanced search'; if (validatedInput.projectSearch && validatedInput.query) { // Handle project search (existing logic) const searchOptions = { fuzzyMatch: validatedInput.fuzzyMatch, includePartialPaths: validatedInput.includePartialPaths, includeFileContent: validatedInput.includeFileContent, minRelevanceScore: validatedInput.minRelevanceScore, orderBy: validatedInput.orderBy, limit: validatedInput.maxResults }; const conversationIds = await reader.getConversationIds({ format: validatedInput.format, projectPath: validatedInput.query }); const conversations = []; const matchTypeDistribution = { exactPath: 0, partialPath: 0, filePath: 0, fuzzy: 0 }; let totalConversationsScanned = 0; let totalRelevanceScore = 0; for (const composerId of conversationIds.slice(0, validatedInput.maxResults * 2)) { try { totalConversationsScanned++; const conversation = await reader.getConversationById(composerId); if (!conversation) continue; const format = conversation.hasOwnProperty('_v') ? 'modern' : 'legacy'; if (format === 'modern') { const modernConv = conversation as any; const headers = modernConv.fullConversationHeadersOnly || []; for (const header of headers.slice(0, 5)) { try { const bubbleMessage = await reader.getBubbleMessage(modernConv.composerId, header.bubbleId); if (bubbleMessage) { (conversation as any).resolvedMessages = (conversation as any).resolvedMessages || []; (conversation as any).resolvedMessages.push(bubbleMessage); } } catch (error) { continue; } } } const relevanceResult = calculateEnhancedProjectRelevance( conversation, validatedInput.query, { fuzzyMatch: validatedInput.fuzzyMatch || false, includePartialPaths: validatedInput.includePartialPaths || false, includeFileContent: validatedInput.includeFileContent || false } ); if (relevanceResult.score >= (validatedInput.minRelevanceScore || 0.1)) { const summary = await reader.getConversationSummary(composerId, { includeFirstMessage: true, maxFirstMessageLength: 150 }); if (summary) { conversations.push({ composerId: summary.composerId, format: summary.format, messageCount: summary.messageCount, hasCodeBlocks: summary.hasCodeBlocks, relevantFiles: summary.relevantFiles || [], attachedFolders: summary.attachedFolders || [], firstMessage: summary.firstMessage, size: summary.conversationSize, relevanceScore: relevanceResult.score, matchDetails: relevanceResult.details }); totalRelevanceScore += relevanceResult.score; if (relevanceResult.details.exactPathMatch) matchTypeDistribution.exactPath++; if (relevanceResult.details.partialPathMatch) matchTypeDistribution.partialPath++; if (relevanceResult.details.filePathMatch) matchTypeDistribution.filePath++; if (relevanceResult.details.fuzzyMatch) matchTypeDistribution.fuzzy++; } } } catch (error) { continue; } } if (validatedInput.orderBy === 'relevance') { conversations.sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0)); } return { conversations: conversations.slice(0, validatedInput.maxResults), totalResults: conversations.length, query: displayQuery, searchOptions: { includeCode: validatedInput.includeCode, contextLines: validatedInput.contextLines, maxResults: validatedInput.maxResults, searchBubbles: validatedInput.searchBubbles, searchType: validatedInput.searchType, format: validatedInput.format, highlightMatches: validatedInput.highlightMatches, projectSearch: validatedInput.projectSearch, fuzzyMatch: validatedInput.fuzzyMatch, includePartialPaths: validatedInput.includePartialPaths, includeFileContent: validatedInput.includeFileContent, minRelevanceScore: validatedInput.minRelevanceScore, orderBy: validatedInput.orderBy }, debugInfo: { totalConversationsScanned, averageRelevanceScore: totalConversationsScanned > 0 ? totalRelevanceScore / totalConversationsScanned : 0, matchTypeDistribution } }; } else { const hasSearchCriteria = (validatedInput.query && validatedInput.query.trim() !== '' && validatedInput.query.trim() !== '?') || validatedInput.keywords || validatedInput.likePattern; if (!hasSearchCriteria && (validatedInput.startDate || validatedInput.endDate)) { // Date-only search: get all conversations and filter by date const allConversationIds = await reader.getConversationIds({ format: validatedInput.format }); const conversations = []; for (const composerId of allConversationIds.slice(0, validatedInput.maxResults * 2)) { try { const conversation = await reader.getConversationById(composerId); if (!conversation) continue; // Apply date filtering const hasValidDate = checkConversationDateRange( conversation, validatedInput.startDate, validatedInput.endDate ); if (!hasValidDate) continue; const summary = await reader.getConversationSummary(composerId, { includeFirstMessage: true, maxFirstMessageLength: 150, includeTitle: true, includeAIGeneratedSummary: true }); if (summary) { conversations.push({ composerId: summary.composerId, format: summary.format, messageCount: summary.messageCount, hasCodeBlocks: summary.hasCodeBlocks, relevantFiles: summary.relevantFiles || [], attachedFolders: summary.attachedFolders || [], firstMessage: summary.firstMessage, title: summary.title, aiGeneratedSummary: summary.aiGeneratedSummary, size: summary.conversationSize }); if (conversations.length >= validatedInput.maxResults) break; } } catch (error) { console.error(`Failed to process conversation ${composerId}:`, error); } } return { conversations, totalResults: conversations.length, query: displayQuery, searchOptions: { includeCode: validatedInput.includeCode, contextLines: validatedInput.contextLines, maxResults: validatedInput.maxResults, searchBubbles: validatedInput.searchBubbles, searchType: validatedInput.searchType, format: validatedInput.format, highlightMatches: validatedInput.highlightMatches } }; } // Handle enhanced search with keywords, LIKE patterns, or simple query const searchResults = await reader.searchConversationsEnhanced({ query: validatedInput.query, keywords: validatedInput.keywords, keywordOperator: validatedInput.keywordOperator, likePattern: validatedInput.likePattern, includeCode: validatedInput.includeCode, contextLines: validatedInput.contextLines, maxResults: validatedInput.maxResults, searchBubbles: validatedInput.searchBubbles, searchType: validatedInput.searchType === 'project' ? 'all' : validatedInput.searchType, format: validatedInput.format, startDate: validatedInput.startDate, endDate: validatedInput.endDate }); // Convert search results to conversation summaries for consistency const conversations = []; for (const result of searchResults) { try { // Apply date filtering if specified (post-query filtering due to unreliable timestamps) if (validatedInput.startDate || validatedInput.endDate) { const conversation = await reader.getConversationById(result.composerId); if (!conversation) continue; const hasValidDate = checkConversationDateRange( conversation, validatedInput.startDate, validatedInput.endDate ); if (!hasValidDate) continue; } const summary = await reader.getConversationSummary(result.composerId, { includeFirstMessage: true, maxFirstMessageLength: 150, includeTitle: true, includeAIGeneratedSummary: true }); if (summary) { conversations.push({ composerId: summary.composerId, format: summary.format, messageCount: summary.messageCount, hasCodeBlocks: summary.hasCodeBlocks, relevantFiles: summary.relevantFiles || [], attachedFolders: summary.attachedFolders || [], firstMessage: summary.firstMessage, title: summary.title, aiGeneratedSummary: summary.aiGeneratedSummary, size: summary.conversationSize }); } } catch (error) { console.error(`Failed to get summary for conversation ${result.composerId}:`, error); } } return { conversations, totalResults: conversations.length, query: displayQuery, searchOptions: { includeCode: validatedInput.includeCode, contextLines: validatedInput.contextLines, maxResults: validatedInput.maxResults, searchBubbles: validatedInput.searchBubbles, searchType: validatedInput.searchType, format: validatedInput.format, highlightMatches: validatedInput.highlightMatches } }; } } finally { reader.close(); } }
- src/server.ts:178-233 (registration)MCP tool registration for 'search_conversations', including tool description, Zod input schema, and wrapper handler that prepares input and calls the core searchConversations function.server.tool( 'search_conversations', 'Searches through Cursor chat content using exact text matching (NOT semantic search) to find relevant discussions. **WARNING: For project-specific searches, use list_conversations with projectPath instead of this tool!** This tool is for searching message content, not project filtering.\n\n**WHEN TO USE THIS TOOL:**\n- Searching for specific technical terms in message content (e.g., "useState", "async/await")\n- Finding conversations mentioning specific error messages\n- Searching for code patterns or function names\n\n**WHEN NOT TO USE THIS TOOL:**\n- ❌ DON\'T use query="project-name" - use list_conversations with projectPath instead\n- ❌ DON\'T search for project names in message content\n- ❌ DON\'T use this for project-specific filtering\n\nSearch methods (all use exact/literal text matching):\n1. Simple text matching: Use query parameter for literal string matching (e.g., "react hooks")\n2. Multi-keyword: Use keywords array with keywordOperator for exact matching\n3. LIKE patterns: Advanced pattern matching with SQL wildcards (% = any chars, _ = single char)\n4. Date range: Filter by message timestamps (YYYY-MM-DD format)\n\nIMPORTANT: When using date filters, call get_system_info first to know today\'s date.\n\nExamples: likePattern="%useState(%" for function calls, keywords=["typescript","interface"] with AND operator.', { query: z.string().optional().describe('Exact text matching - searches for literal string occurrences in MESSAGE CONTENT (e.g., "react hooks", "useState", "error message"). ❌ DON\'T use for project names - use list_conversations with projectPath instead!'), keywords: z.array(z.string().min(1)).optional().describe('Array of keywords for exact text matching - use with keywordOperator to find conversations with specific combinations'), keywordOperator: z.enum(['AND', 'OR']).optional().default('OR').describe('How to combine keywords: "AND" = all keywords must be present, "OR" = any keyword can be present'), likePattern: z.string().optional().describe('SQL LIKE pattern for advanced searches - use % for any characters, _ for single character. Examples: "%useState(%" for function calls, "%.tsx%" for file types'), startDate: z.string().optional().describe('Start date for search (YYYY-MM-DD). Note: Timestamps may be unreliable.'), endDate: z.string().optional().describe('End date for search (YYYY-MM-DD). Note: Timestamps may be unreliable.'), searchType: z.enum(['all', 'project', 'files', 'code']).optional().default('all').describe('Focus search on specific content types. Use "project" for project-specific searches that leverage file path context.'), maxResults: z.number().min(1).max(50).optional().default(10).describe('Maximum number of conversations to return'), includeCode: z.boolean().optional().default(true).describe('Include code blocks in search results'), outputMode: z.enum(['json', 'compact-json']).optional().default('json').describe('Output format: "json" for formatted JSON (default), "compact-json" for minified JSON') }, async (input) => { try { const hasSearchCriteria = (input.query && input.query.trim() !== '' && input.query.trim() !== '?') || input.keywords || input.likePattern; const hasDateFilter = input.startDate || input.endDate; const hasOtherFilters = input.searchType !== 'all'; if (!hasSearchCriteria && !hasDateFilter && !hasOtherFilters) { throw new Error('At least one search criteria (query, keywords, likePattern), date filter (startDate, endDate), or search type filter must be provided'); } const fullInput = { ...input, contextLines: 2, searchBubbles: true, format: 'both' as const, highlightMatches: true, projectSearch: input.searchType === 'project', fuzzyMatch: input.searchType === 'project', includePartialPaths: input.searchType === 'project', includeFileContent: false, minRelevanceScore: 0.1, orderBy: 'recency' as const }; const result = await searchConversations(fullInput); return { content: [{ type: 'text', text: formatResponse(result, input.outputMode) }] }; } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` }] }; } } );
- Internal Zod schema for validating inputs to the searchConversations handler function, supporting advanced search parameters.export const searchConversationsSchema = z.object({ // Simple query (existing - backward compatible) query: z.string().optional(), // Multi-keyword search keywords: z.array(z.string().min(1)).optional(), keywordOperator: z.enum(['AND', 'OR']).optional().default('OR'), // LIKE pattern search (database-level) likePattern: z.string().optional(), // Date filtering startDate: z.string().optional(), endDate: z.string().optional(), // Existing options includeCode: z.boolean().optional().default(true), contextLines: z.number().min(0).max(10).optional().default(2), maxResults: z.number().min(1).max(100).optional().default(10), searchBubbles: z.boolean().optional().default(true), searchType: z.enum(['all', 'summarization', 'code', 'files', 'project']).optional().default('all'), format: z.enum(['legacy', 'modern', 'both']).optional().default('both'), highlightMatches: z.boolean().optional().default(true), projectSearch: z.boolean().optional().default(false), fuzzyMatch: z.boolean().optional().default(false), includePartialPaths: z.boolean().optional().default(true), includeFileContent: z.boolean().optional().default(false), minRelevanceScore: z.number().min(0).max(1).optional().default(0.1), orderBy: z.enum(['relevance', 'recency']).optional().default('relevance') }).refine( (data) => { const hasSearchCriteria = (data.query && data.query.trim() !== '' && data.query.trim() !== '?') || data.keywords || data.likePattern; const hasDateFilter = data.startDate || data.endDate; const hasOtherFilters = data.searchType !== 'all'; return hasSearchCriteria || hasDateFilter || hasOtherFilters; }, { message: "At least one search criteria (query, keywords, likePattern), date filter (startDate, endDate), or search type filter must be provided" } );
- src/server.ts:182-192 (schema)Zod schema defining the input parameters for the MCP 'search_conversations' tool as registered in server.ts.query: z.string().optional().describe('Exact text matching - searches for literal string occurrences in MESSAGE CONTENT (e.g., "react hooks", "useState", "error message"). ❌ DON\'T use for project names - use list_conversations with projectPath instead!'), keywords: z.array(z.string().min(1)).optional().describe('Array of keywords for exact text matching - use with keywordOperator to find conversations with specific combinations'), keywordOperator: z.enum(['AND', 'OR']).optional().default('OR').describe('How to combine keywords: "AND" = all keywords must be present, "OR" = any keyword can be present'), likePattern: z.string().optional().describe('SQL LIKE pattern for advanced searches - use % for any characters, _ for single character. Examples: "%useState(%" for function calls, "%.tsx%" for file types'), startDate: z.string().optional().describe('Start date for search (YYYY-MM-DD). Note: Timestamps may be unreliable.'), endDate: z.string().optional().describe('End date for search (YYYY-MM-DD). Note: Timestamps may be unreliable.'), searchType: z.enum(['all', 'project', 'files', 'code']).optional().default('all').describe('Focus search on specific content types. Use "project" for project-specific searches that leverage file path context.'), maxResults: z.number().min(1).max(50).optional().default(10).describe('Maximum number of conversations to return'), includeCode: z.boolean().optional().default(true).describe('Include code blocks in search results'), outputMode: z.enum(['json', 'compact-json']).optional().default('json').describe('Output format: "json" for formatted JSON (default), "compact-json" for minified JSON') },