Skip to main content
Glama
199-mcp

Limitless MCP Server

by 199-mcp

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

TableJSON Schema
NameRequiredDescriptionDefault
time_expressionNoNatural time expression like 'today', 'this week' (defaults to 'today').
timezoneNoIANA timezone for date calculations.
assigned_toNoFilter action items by assignee ('user' for items assigned to you).
priorityNoFilter 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 };
            }
        }
    );
  • 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 };
        }
    }
  • 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."),
    };
  • 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);
    }
  • 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;
            });
        }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'intelligently extract' and 'smart pattern recognition,' which hint at AI-driven processing, but lacks details on error handling, rate limits, authentication needs, or output format. For a tool with no annotations and complex functionality, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: the first sentence states the core functionality, and the second adds context. Both sentences are relevant, with no wasted words. However, it could be slightly more structured (e.g., separating capabilities from use cases).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (AI-driven extraction with 4 parameters), no annotations, and no output schema, the description is incomplete. It doesn't explain what the output looks like (e.g., list of action items with details), error conditions, or how extraction works with the parameters. More context is needed for effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 4 parameters. The description doesn't add any parameter-specific information beyond what's in the schema (e.g., it doesn't explain how 'time_expression' interacts with extraction). Baseline 3 is appropriate as the schema handles parameter semantics adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'extract action items and tasks from conversations' with specific capabilities like 'context, priority analysis, and smart pattern recognition.' It distinguishes from siblings by focusing on task extraction rather than analysis, detection, or retrieval of transcripts/lifelogs. However, it doesn't explicitly differentiate from all siblings (e.g., 'limitless_analyze_speaker' might also involve conversation analysis).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides minimal usage guidance: 'Perfect for getting your todo list from meetings' implies use in meeting contexts, but it doesn't specify when to use this tool versus alternatives like 'limitless_get_detailed_analysis' or 'limitless_search_conversations_about' for similar purposes. No explicit when-not-to-use or prerequisite information is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/199-mcp/mcp-limitless'

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