limitless_extract_action_items
Extract action items and tasks from conversations with priority analysis and context recognition to create organized to-do lists from meetings.
Instructions
Intelligently extract action items and tasks from conversations with context, priority analysis, and smart pattern recognition. Perfect for getting your todo list from meetings.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| time_expression | No | Natural time expression like 'today', 'this week' (defaults to 'today'). | |
| timezone | No | IANA timezone for date calculations. | |
| assigned_to | No | Filter action items by assignee ('user' for items assigned to you). | |
| priority | No | Filter by priority level. |
Implementation Reference
- src/server.ts:851-912 (registration)Registration of the 'limitless_extract_action_items' tool with MCP server, including description, schema reference, and handler callback.
server.tool("limitless_extract_action_items", "Intelligently extract action items and tasks from conversations with context, priority analysis, and smart pattern recognition. Perfect for getting your todo list from meetings.", ActionItemsArgsSchema, async (args, _extra) => { try { const timeExpression = args.time_expression || 'today'; const parser = new NaturalTimeParser({ timezone: args.timezone }); const timeRange = parser.parseTimeExpression(timeExpression); // Fetch all logs with pagination const allLogs: Lifelog[] = []; let cursor: string | undefined = undefined; while (true) { const result = await getLifelogsWithPagination(limitlessApiKey, { start: timeRange.start, end: timeRange.end, timezone: timeRange.timezone, includeMarkdown: true, includeHeadings: true, limit: MAX_API_LIMIT, direction: 'asc', cursor: cursor }); allLogs.push(...result.lifelogs); if (!result.pagination.nextCursor || result.lifelogs.length < MAX_API_LIMIT) { break; } cursor = result.pagination.nextCursor; } let allActionItems: ActionItem[] = []; for (const lifelog of allLogs) { if (lifelog.contents) { const items = ActionItemExtractor.extractFromNodes(lifelog.contents, lifelog.id); allActionItems.push(...items); } } // Apply filters if (args.assigned_to) { allActionItems = allActionItems.filter(item => item.assignee === args.assigned_to); } if (args.priority) { allActionItems = allActionItems.filter(item => item.priority === args.priority); } return createSafeResponse( allActionItems, allActionItems.length === 0 ? `No action items found for "${timeExpression}"` : `Found ${allActionItems.length} action item(s) for "${timeExpression}"` ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error extracting action items: ${errorMessage}` }], isError: true }; } } ); - src/server.ts:854-911 (handler)Handler function that orchestrates lifelog fetching via pagination, action item extraction using ActionItemExtractor, filtering by assignee and priority, and safe response formatting with truncation handling.
async (args, _extra) => { try { const timeExpression = args.time_expression || 'today'; const parser = new NaturalTimeParser({ timezone: args.timezone }); const timeRange = parser.parseTimeExpression(timeExpression); // Fetch all logs with pagination const allLogs: Lifelog[] = []; let cursor: string | undefined = undefined; while (true) { const result = await getLifelogsWithPagination(limitlessApiKey, { start: timeRange.start, end: timeRange.end, timezone: timeRange.timezone, includeMarkdown: true, includeHeadings: true, limit: MAX_API_LIMIT, direction: 'asc', cursor: cursor }); allLogs.push(...result.lifelogs); if (!result.pagination.nextCursor || result.lifelogs.length < MAX_API_LIMIT) { break; } cursor = result.pagination.nextCursor; } let allActionItems: ActionItem[] = []; for (const lifelog of allLogs) { if (lifelog.contents) { const items = ActionItemExtractor.extractFromNodes(lifelog.contents, lifelog.id); allActionItems.push(...items); } } // Apply filters if (args.assigned_to) { allActionItems = allActionItems.filter(item => item.assignee === args.assigned_to); } if (args.priority) { allActionItems = allActionItems.filter(item => item.priority === args.priority); } return createSafeResponse( allActionItems, allActionItems.length === 0 ? `No action items found for "${timeExpression}"` : `Found ${allActionItems.length} action item(s) for "${timeExpression}"` ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error extracting action items: ${errorMessage}` }], isError: true }; } } - src/server.ts:258-263 (schema)Zod schema defining input parameters for the tool: time_expression (optional, defaults today), timezone, assigned_to filter, and priority filter.
const ActionItemsArgsSchema = { time_expression: z.string().optional().describe("Natural time expression like 'today', 'this week' (defaults to 'today')."), timezone: z.string().optional().describe("IANA timezone for date calculations."), assigned_to: z.string().optional().describe("Filter action items by assignee ('user' for items assigned to you)."), priority: z.enum(["high", "medium", "low"]).optional().describe("Filter by priority level."), }; - src/advanced-features.ts:1042-1073 (helper)Primary helper method that scans LifelogContentNode contents using regex patterns to detect action items, extracts assignee from speaker info, surrounding context, infers priority, and deduplicates results.
static extractFromNodes(nodes: LifelogContentNode[], lifelogId: string): ActionItem[] { const actionItems: ActionItem[] = []; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (!node.content) continue; for (const pattern of this.ACTION_PATTERNS) { const matches = Array.from(node.content.matchAll(pattern)); for (const match of matches) { const content = (match[2] || match[1]).trim(); if (content.length > 5) { // Filter out very short matches actionItems.push({ content, assignee: node.speakerIdentifier === "user" ? "user" : node.speakerName || undefined, context: this.getContext(nodes, i), timestamp: node.startTime || "", priority: this.inferPriority(content), source: { lifelogId, nodeIndex: i } }); } } } } return this.deduplicateActionItems(actionItems); } - src/advanced-features.ts:1022-1117 (helper)ActionItemExtractor class containing regex patterns for action detection, context extraction, priority inference (inferPriority), and deduplication logic.
export class ActionItemExtractor { private static readonly ACTION_PATTERNS = [ // Direct commitments with technical context /\b(I'll|I will|I need to|I should|I must)\s+([^.!?]{5,150})/gi, // Explicit action items with detailed context /\b(todo|to do|action item|task|deliverable):\s*([^.!?]{5,200})/gi, // Follow-up actions with specifics /\b(follow up|follow-up)\s+(on|with)\s+([^.!?]{5,150})/gi, // Specific actions with verbs and context /\b(need to|should|must|will|have to)\s+(send|call|email|schedule|review|update|create|finish|complete|implement|develop|test|deploy|analyze|research|investigate|prepare|draft|submit|approve)\s+([^.!?]{5,200})/gi, // Deadlines with detailed context /\b(by\s+(?:next\s+)?(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|week|month)|by\s+\d+|before\s+\d+|deadline|due)\s*:?\s*([^.!?]{10,200})/gi, // Technical implementation tasks /\b(implement|develop|build|code|write|design|architect|deploy|configure|setup|install|upgrade|migrate|optimize|refactor|debug|fix|patch)\s+([^.!?]{10,200})/gi, // Research and analysis tasks /\b(research|investigate|analyze|evaluate|assess|review|study|examine|explore|compare)\s+([^.!?]{10,200})/gi, // Decision-making commitments /\b(decide|determine|choose|select|finalize|confirm|approve|reject)\s+([^.!?]{10,200})/gi ]; static extractFromNodes(nodes: LifelogContentNode[], lifelogId: string): ActionItem[] { const actionItems: ActionItem[] = []; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (!node.content) continue; for (const pattern of this.ACTION_PATTERNS) { const matches = Array.from(node.content.matchAll(pattern)); for (const match of matches) { const content = (match[2] || match[1]).trim(); if (content.length > 5) { // Filter out very short matches actionItems.push({ content, assignee: node.speakerIdentifier === "user" ? "user" : node.speakerName || undefined, context: this.getContext(nodes, i), timestamp: node.startTime || "", priority: this.inferPriority(content), source: { lifelogId, nodeIndex: i } }); } } } } return this.deduplicateActionItems(actionItems); } private static getContext(nodes: LifelogContentNode[], currentIndex: number): string { const contextRange = 3; // Increased for more context const start = Math.max(0, currentIndex - contextRange); const end = Math.min(nodes.length, currentIndex + contextRange + 1); const contextNodes = nodes.slice(start, end); let context = ""; // Build rich context with speakers and technical details for (let i = 0; i < contextNodes.length; i++) { const node = contextNodes[i]; if (!node.content || !node.content.trim()) continue; const isCurrentNode = (start + i) === currentIndex; const speaker = node.speakerName ? `${node.speakerName}: ` : ""; const marker = isCurrentNode ? ">>> " : ""; context += `${marker}${speaker}${node.content.trim()} `; } // Preserve technical terms, numbers, and specific details in context return context.slice(0, 400).trim() + (context.length > 400 ? "..." : ""); } private static inferPriority(content: string): "high" | "medium" | "low" { const highPriorityKeywords = /\b(urgent|asap|immediately|critical|important|deadline|by tomorrow|by today)\b/i; const mediumPriorityKeywords = /\b(soon|this week|by friday|follow up|review)\b/i; if (highPriorityKeywords.test(content)) return "high"; if (mediumPriorityKeywords.test(content)) return "medium"; return "low"; } private static deduplicateActionItems(items: ActionItem[]): ActionItem[] { const seen = new Set<string>(); return items.filter(item => { const key = item.content.toLowerCase().trim(); if (seen.has(key)) return false; seen.add(key); return true; }); } }