list_conversations
Retrieve and filter Cursor chat histories by project, keywords, or date range to analyze conversations with AI-generated summaries, titles, and metadata. Supports project-specific filtering to focus on relevant codebase discussions.
Instructions
Lists Cursor chats with summaries, titles, and metadata ordered by recency. HIGHLY RECOMMENDED: Use projectPath parameter to filter conversations by specific project/codebase - this dramatically improves relevance by finding conversations that actually worked on files in that project. Returns conversation IDs for use with get_conversation tool. WORKFLOW TIP: Start with projectPath filtering for project-specific analysis, then call get_conversation with specific IDs from results. Includes AI-generated summaries by default. Supports date range filtering (YYYY-MM-DD format).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| endDate | No | End date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable. | |
| filePattern | No | Filter conversations mentioning files matching this pattern (e.g., "*.tsx") | |
| hasCodeBlocks | No | Filter to conversations that contain code blocks | |
| includeAiSummaries | No | Include AI-generated conversation summaries | |
| includeEmpty | No | Include conversations with no messages | |
| includeRelevanceScore | No | Include relevance scores when filtering by projectPath | |
| keywords | No | Filter conversations containing any of these exact keywords (literal text matching) | |
| limit | No | Maximum number of conversations to return (1-100) | |
| minLength | No | Minimum conversation length in characters to include | |
| outputMode | No | Output format: "json" for formatted JSON (default), "compact-json" for minified JSON | json |
| projectPath | No | **RECOMMENDED** Filter conversations by project/codebase name (e.g., "my-app") or full path (e.g., "/Users/name/Projects/my-app"). This finds conversations that actually worked on files in that project, dramatically improving relevance for project-specific analysis. | |
| relevantFiles | No | Filter conversations that reference any of these specific files | |
| startDate | No | Start date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable. |
Implementation Reference
- src/server.ts:51-140 (registration)Registration of the 'list_conversations' MCP tool, including description, inline input schema, and async handler that conditionally calls listConversations or getConversationsByProject based on projectPath and includeRelevanceScore flags.server.tool( 'list_conversations', 'Lists Cursor chats with summaries, titles, and metadata ordered by recency. **HIGHLY RECOMMENDED: Use projectPath parameter to filter conversations by specific project/codebase** - this dramatically improves relevance by finding conversations that actually worked on files in that project. Returns conversation IDs for use with get_conversation tool. WORKFLOW TIP: Start with projectPath filtering for project-specific analysis, then call get_conversation with specific IDs from results. Includes AI-generated summaries by default. Supports date range filtering (YYYY-MM-DD format).', { limit: z.number().min(1).max(100).optional().default(10).describe('Maximum number of conversations to return (1-100)'), minLength: z.number().min(0).optional().default(100).describe('Minimum conversation length in characters to include'), hasCodeBlocks: z.boolean().optional().describe('Filter to conversations that contain code blocks'), keywords: z.array(z.string()).optional().describe('Filter conversations containing any of these exact keywords (literal text matching)'), projectPath: z.string().optional().describe('**RECOMMENDED** Filter conversations by project/codebase name (e.g., "my-app") or full path (e.g., "/Users/name/Projects/my-app"). This finds conversations that actually worked on files in that project, dramatically improving relevance for project-specific analysis.'), filePattern: z.string().optional().describe('Filter conversations mentioning files matching this pattern (e.g., "*.tsx")'), relevantFiles: z.array(z.string()).optional().describe('Filter conversations that reference any of these specific files'), startDate: z.string().optional().describe('Start date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable.'), endDate: z.string().optional().describe('End date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable.'), includeEmpty: z.boolean().optional().default(false).describe('Include conversations with no messages'), includeAiSummaries: z.boolean().optional().default(true).describe('Include AI-generated conversation summaries'), includeRelevanceScore: z.boolean().optional().default(false).describe('Include relevance scores when filtering by projectPath'), 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 { if (input.projectPath && input.includeRelevanceScore) { const projectInput = { projectPath: input.projectPath, filePattern: input.filePattern, orderBy: 'recency' as const, limit: input.limit, fuzzyMatch: false }; const result = await getConversationsByProject(projectInput); const transformedResult = { conversations: result.conversations.map(conv => ({ ...conv, title: undefined, aiGeneratedSummary: undefined, relevanceScore: conv.relevanceScore })), totalFound: result.totalFound, filters: { limit: input.limit ?? 10, minLength: input.minLength ?? 100, hasCodeBlocks: input.hasCodeBlocks, keywords: input.keywords, projectPath: input.projectPath, filePattern: input.filePattern, relevantFiles: input.relevantFiles, includeAiSummaries: input.includeAiSummaries } }; return { content: [{ type: 'text', text: formatResponse(transformedResult, input.outputMode) }] }; } else { const mappedInput = { limit: input.limit, minLength: input.minLength, format: 'both' as const, hasCodeBlocks: input.hasCodeBlocks, keywords: input.keywords, projectPath: input.projectPath, filePattern: input.filePattern, relevantFiles: input.relevantFiles, startDate: input.startDate, endDate: input.endDate, includeEmpty: input.includeEmpty, includeAiSummaries: input.includeAiSummaries }; const result = await listConversations(mappedInput); 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'}` }] }; } } );
- src/tools/conversation-tools.ts:56-159 (handler)Core handler function that executes the list_conversations logic: validates input, connects to Cursor database, applies filters (including projectPath, keywords, dates), fetches conversation IDs and summaries, handles date post-filtering, and returns formatted output with totals and applied filters.export async function listConversations(input: ListConversationsInput): Promise<ListConversationsOutput> { const validatedInput = listConversationsSchema.parse(input); const dbPath = process.env.CURSOR_DB_PATH || detectCursorDatabasePath(); const reader = new CursorDatabaseReader({ dbPath }); try { await reader.connect(); const filters: ConversationFilters = { minLength: validatedInput.minLength, format: validatedInput.format, hasCodeBlocks: validatedInput.hasCodeBlocks, keywords: validatedInput.keywords, projectPath: validatedInput.projectPath, filePattern: validatedInput.filePattern, relevantFiles: validatedInput.relevantFiles }; // Add date range filter if provided if (validatedInput.startDate || validatedInput.endDate) { const start = validatedInput.startDate ? new Date(validatedInput.startDate) : new Date('1970-01-01'); const end = validatedInput.endDate ? new Date(validatedInput.endDate) : new Date(); filters.dateRange = { start, end }; } const conversationIds = await reader.getConversationIds(filters); let limitedIds = conversationIds.slice(0, validatedInput.limit); // Apply date filtering if specified (post-query filtering due to unreliable timestamps) if (validatedInput.startDate || validatedInput.endDate) { const filteredIds = []; for (const composerId of limitedIds) { try { const conversation = await reader.getConversationById(composerId); if (!conversation) continue; const hasValidDate = checkConversationDateRange( conversation, validatedInput.startDate, validatedInput.endDate ); if (hasValidDate) { filteredIds.push(composerId); } } catch (error) { // Skip conversations that can't be processed continue; } } limitedIds = filteredIds; } const conversations = []; for (const composerId of limitedIds) { try { const summary = await reader.getConversationSummary(composerId, { includeFirstMessage: true, maxFirstMessageLength: 150, includeTitle: true, includeAIGeneratedSummary: validatedInput.includeAiSummaries }); 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 ${composerId}:`, error); } } return { conversations, totalFound: conversationIds.length, filters: { limit: validatedInput.limit ?? 10, minLength: validatedInput.minLength ?? 100, format: validatedInput.format ?? 'both', hasCodeBlocks: validatedInput.hasCodeBlocks, keywords: validatedInput.keywords, projectPath: validatedInput.projectPath, filePattern: validatedInput.filePattern, relevantFiles: validatedInput.relevantFiles, includeAiSummaries: validatedInput.includeAiSummaries } }; } finally { // Always close the database connection reader.close(); } }
- src/tools/conversation-tools.ts:7-21 (schema)Zod input schema defining parameters for listConversations function (exported for type safety).// Input schema for list_conversations tool export const listConversationsSchema = z.object({ limit: z.number().min(1).max(1000).optional(), minLength: z.number().min(0).optional(), keywords: z.array(z.string()).optional(), hasCodeBlocks: z.boolean().optional(), format: z.enum(['legacy', 'modern', 'both']).optional(), includeEmpty: z.boolean().optional(), projectPath: z.string().optional(), filePattern: z.string().optional(), relevantFiles: z.array(z.string()).optional(), startDate: z.string().optional(), endDate: z.string().optional(), includeAiSummaries: z.boolean().optional().default(true) });
- src/server.ts:55-67 (schema)Inline Zod schema used in MCP tool registration for input validation and descriptions.limit: z.number().min(1).max(100).optional().default(10).describe('Maximum number of conversations to return (1-100)'), minLength: z.number().min(0).optional().default(100).describe('Minimum conversation length in characters to include'), hasCodeBlocks: z.boolean().optional().describe('Filter to conversations that contain code blocks'), keywords: z.array(z.string()).optional().describe('Filter conversations containing any of these exact keywords (literal text matching)'), projectPath: z.string().optional().describe('**RECOMMENDED** Filter conversations by project/codebase name (e.g., "my-app") or full path (e.g., "/Users/name/Projects/my-app"). This finds conversations that actually worked on files in that project, dramatically improving relevance for project-specific analysis.'), filePattern: z.string().optional().describe('Filter conversations mentioning files matching this pattern (e.g., "*.tsx")'), relevantFiles: z.array(z.string()).optional().describe('Filter conversations that reference any of these specific files'), startDate: z.string().optional().describe('Start date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable.'), endDate: z.string().optional().describe('End date for filtering (YYYY-MM-DD). Note: Timestamps may be unreliable.'), includeEmpty: z.boolean().optional().default(false).describe('Include conversations with no messages'), includeAiSummaries: z.boolean().optional().default(true).describe('Include AI-generated conversation summaries'), includeRelevanceScore: z.boolean().optional().default(false).describe('Include relevance scores when filtering by projectPath'), outputMode: z.enum(['json', 'compact-json']).optional().default('json').describe('Output format: "json" for formatted JSON (default), "compact-json" for minified JSON')