Skip to main content
Glama
anortham

COA Goldfish MCP

by anortham

view_todos

Check active TODO lists and their progress on the COA Goldfish MCP server. Optionally view specific lists or include completed items for a comprehensive task overview.

Instructions

View active TODO lists and their progress. Perfect for checking current status.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
listIdNoSpecific list ID to view (optional)
showCompletedNoInclude completed items (default: true)

Implementation Reference

  • Main execution logic for the view_todos tool. Handles viewing specific TODO lists or all lists with smart filtering, background cleanup of old lists, progress calculation, and formatted output in multiple modes.
    export async function handleViewTodos(storage: Storage, args: ViewTodosArgs): Promise<ToolResponse> {
      // Validate input (args can be empty/undefined for this tool)
      if (args !== undefined && args !== null) {
        const validation = validateCommonArgs(args);
        if (!validation.isValid) {
          return createErrorResponse(validation.error!, 'view_todos', 'emoji');
        }
      }
    
      const safeArgs = args || {};
      const { listId, scope = 'current', format, summary = false } = safeArgs;
    
      if (listId) {
        // View specific list 
        const todoLists = await loadTodoListsWithScope(storage, scope);
        
        // Use the resolver to handle "latest" and other special keywords
        const targetList = resolveSpecialTodoListId(listId, todoLists);
        
        if (!targetList) {
          // Provide helpful error message for special keywords
          const isSpecialKeyword = ['latest', 'recent', 'last', 'active', 'current'].includes(listId.toLowerCase().trim());
          if (isSpecialKeyword) {
            return createErrorResponse(`📋 No ${listId} TODO list found. Try using "latest", "active", or create a new list first.`, 'view_todos', 'emoji');
          }
          return createErrorResponse(`📋 TODO list "${listId}" not found. Available options: use "latest" for recent list, "active" for current work, or omit listId to see all lists.`, 'view_todos', 'emoji');
        }
        
        // Sort items by ID number (1,2,3,4,5,6,7) regardless of status
        const listItems = [...targetList.items].sort((a, b) => {
          return parseInt(a.id) - parseInt(b.id);
        });
    
        // Build formatted output for specific list
        const output = [];
        
        const completedCount = listItems.filter(i => i.status === 'done').length;
        const activeCount = listItems.filter(i => i.status === 'active').length;
        const percentage = calculatePercentage(completedCount, listItems.length);
        
        const workspaceLabel = formatWorkspaceLabel(targetList.workspace, storage.getCurrentWorkspace(), scope);
        
        output.push(`📋 ${targetList.title}${workspaceLabel}`);
        output.push(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
        output.push(`📊 Progress: ${percentage}% (${completedCount}/${listItems.length}) • Active: ${activeCount}`);
        output.push(``);
        
        // Each todo item
        for (const item of listItems) {
          const icon = getTaskStatusIcon(item.status);
          const taskText = truncateText(item.task, 80);
          output.push(`${icon} [${item.id}] ${taskText}`);
        }
        
        // Create structured response
        const data = {
          listId: targetList.id,
          title: targetList.title,
          totalTasks: listItems.length,
          completedTasks: completedCount,
          activeTasks: activeCount,
          percentage: percentage,
          items: listItems.map(item => ({
            id: item.id,
            task: item.task,
            status: item.status
          }))
        };
    
        // For specific list details, prefer JSON for easier parsing in tests/consumers unless explicitly overridden
        return createStructuredResponse('view-todos', output.join('\n'), data, undefined, format || 'json');
      }
    
      let todoLists = await loadTodoListsWithScope(storage, scope);
      
      // Background cleanup - silently maintain list lifecycle
      await performBackgroundCleanup(storage, todoLists);
      
      // Reload to get cleaned up lists
      todoLists = await loadTodoListsWithScope(storage, scope);
      
      if (todoLists.length === 0) {
        const scopeText = scope === 'all' ? ' across all workspaces' : '';
        return {
          content: [
            {
              type: 'text',
              text: `📝 No active TODO lists found${scopeText}. Use create_todo_list to start tracking your work!`
            }
          ]
        };
      }
    
      // Show ALL todo lists with summary information
      // Sort: incomplete lists first, then by most recent update
      const sortedLists = todoLists.sort((a, b) => {
        const aIncomplete = a.items.some((item: any) => item.status !== 'done');
        const bIncomplete = b.items.some((item: any) => item.status !== 'done');
        
        // Incomplete lists first
        if (aIncomplete && !bIncomplete) return -1;
        if (!aIncomplete && bIncomplete) return 1;
        
        // Within same completion state, sort by most recent update
        return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
      });
    
      // Check if we need summary mode for token limit management
      if (summary || sortedLists.length > 10) {
        // Build compact summary for standup/large datasets
        const output = [];
        const now = new Date();
        const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
        
        const relevantLists = sortedLists.filter(list => {
          const lastUpdate = new Date(list.updatedAt);
          const hasPendingTasks = list.items.some((item: any) => item.status !== 'done');
          
          // Show lists that are:
          // 1. Recently updated (within 24h) OR
          // 2. Active status with pending tasks OR  
          // 3. Have no explicit status (legacy) with pending tasks
          return (
            lastUpdate > twentyFourHoursAgo ||
            (list.status === 'active' && hasPendingTasks) ||
            (!list.status && hasPendingTasks)
          );
        });
        
        if (relevantLists.length === 0) {
          output.push(`📋 No active or recent TODO lists found! 🎉`);
        } else {
          output.push(`📋 ${relevantLists.length} active/recent TODO lists (${todoLists.length} total)`);
          
          // Show only relevant lists with basic info, including partial IDs for cleanup
          for (const list of relevantLists.slice(0, 5)) {
            const pendingTasks = list.items.filter((item: any) => item.status !== 'done');
            const workspaceLabel = formatWorkspaceLabel(list.workspace, storage.getCurrentWorkspace(), scope);
            const statusIcon = list.status === 'completed' ? '✅' : (pendingTasks.length === 0 ? '✅' : '📝');
            const partialId = getPartialId(list.id);
            output.push(`${statusIcon} ${list.title}${workspaceLabel} [${partialId}]: ${pendingTasks.length} pending`);
          }
          
          if (relevantLists.length > 5) {
            output.push(`... and ${relevantLists.length - 5} more relevant lists`);
          }
        }
        
        output.push(`💡 Use view_todos({ listId: "latest" }) for details`);
        
        // Minimal data for summary mode  
        const data = {
          totalLists: todoLists.length,
          activeLists: relevantLists.length,
          completedLists: todoLists.length - relevantLists.length,
          summaryMode: true,
          filteredView: true // Indicate this is showing only active/recent
        };
    
        return createStructuredResponse('view-todos', output.join('\n'), data, undefined, format || 'json');
      }
    
      // Full detailed view for smaller datasets
      const output = [];
      
      output.push(`📋 Active TODO Lists (${todoLists.length} found)`);
      output.push(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
      
      // Show each list with summary
      for (let i = 0; i < sortedLists.length; i++) {
        const list = sortedLists[i];
        if (!list) continue; // Skip null/undefined entries
        
        const completedCount = list.items.filter((item: any) => item.status === 'done').length;
        const totalCount = list.items.length;
        const percentage = calculatePercentage(completedCount, totalCount);
        const pendingCount = totalCount - completedCount;
        
        const workspaceLabel = formatWorkspaceLabel(list.workspace, storage.getCurrentWorkspace(), scope);
        
        const numberIcon = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣'][i] || `${i + 1}️⃣`;
        
        output.push(`${numberIcon} ${list.title}${workspaceLabel} [${getPartialId(list.id)}]`);
        output.push(`   ID: ${list.id}`);
        
        if (percentage === 100) {
          output.push(`   📊 100% (${completedCount}/${totalCount}) • ✅ Complete`);
        } else {
          output.push(`   📊 ${percentage}% (${completedCount}/${totalCount}) • ${pendingCount} pending tasks`);
        }
        
        if (i < sortedLists.length - 1) {
          output.push(``); // Blank line between lists
        }
      }
      
      output.push(``);
      output.push(`💡 Use view_todos({ listId: "..." }) to see specific list details`);
      
      // Create structured response with proper formatting
      const data = {
        totalLists: todoLists.length,
        lists: sortedLists.map(list => {
          const completedTasks = list.items.filter((item: any) => item.status === 'done').length;
          const totalTasks = list.items.length;
          return {
            id: list.id,
            title: list.title,
            totalTasks,
            completedTasks,
            activeTasks: list.items.filter((item: any) => item.status === 'active').length,
            percentage: calculatePercentage(completedTasks, totalTasks),
            workspace: list.workspace
          };
        })
      };
    
      // Summary view: prefer JSON for stable parsing in tests/consumers unless explicitly overridden
      return createStructuredResponse('view-todos', output.join('\n'), data, undefined, format || 'json');
    }
  • Defines the complete tool schema for 'view_todos' including name, description, and input schema validation structure.
    export function getViewTodosToolSchema() {
      return {
        name: 'view_todos',
        description: 'ALWAYS check TODO lists when starting work or after completing tasks. Shows progress and pending items. Use PROACTIVELY to stay organized and track work. Smart filtering shows only active/recent lists by default. Auto-summary mode for large datasets.',
        inputSchema: {
          type: 'object',
          properties: {
            listId: {
              type: 'string',
              description: 'Specific list ID to view (optional)'
            },
            showCompleted: {
              type: 'boolean',
              description: 'Include completed items (default: true)',
              default: true
            },
            scope: {
              type: 'string',
              enum: ['current', 'all'],
              description: 'Search scope: current workspace or all workspaces (default: current)',
              default: 'current'
            },
            format: {
              type: 'string',
              enum: ['plain', 'emoji', 'json', 'dual'],
              description: 'Output format override (defaults to env GOLDFISH_OUTPUT_MODE or dual)'
            },
            summary: {
              type: 'boolean',
              description: 'Show compact summary view for token limit management (default: false, auto-enabled for >10 lists)',
              default: false
            }
          }
        }
      };
    }
  • TypeScript interface defining the expected input parameters for the view_todos handler.
    export interface ViewTodosArgs {
      listId?: string;
      showCompleted?: boolean;
      scope?: 'current' | 'all';
      format?: import('../core/output-utils.js').OutputMode;
      summary?: boolean;
    }
  • Background utility that automatically manages TODO list lifecycle: completes done lists, archives stalled ones, and prunes old archives.
    async function performBackgroundCleanup(storage: Storage, todoLists: any[]): Promise<void> {
      const now = new Date();
      const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
      const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
      
      for (const list of todoLists) {
        let needsSave = false;
        const lastUpdate = new Date(list.updatedAt);
        
        // 1. Auto-complete lists where all tasks are done
        const allItemsDone = list.items.length > 0 && list.items.every((item: any) => item.status === 'done');
        if (allItemsDone && list.status !== 'completed') {
          list.status = 'completed';
          list.completedAt = now;
          list.updatedAt = now;
          needsSave = true;
        }
        
        // 2. Archive lists inactive for 7+ days with pending tasks
        else if (list.status === 'active' && lastUpdate < sevenDaysAgo && !allItemsDone) {
          list.status = 'archived';
          list.archivedAt = now;
          list.updatedAt = now;
          needsSave = true;
        }
        
        // 3. Delete archived lists older than 30 days
        else if (list.status === 'archived' && lastUpdate < thirtyDaysAgo) {
          try {
            await storage.deleteTodoList(list.id, list.workspace);
            continue; // Skip save since we deleted
          } catch (error) {
            // Ignore deletion errors, list will be retried next time
          }
        }
        
        if (needsSave) {
          try {
            await storage.saveTodoList(list);
          } catch (error) {
            // Ignore save errors to prevent blocking the view operation
          }
        }
      }
    }
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 'viewing' and 'checking current status,' which implies a read-only operation, but doesn't specify permissions, rate limits, error handling, or what the output looks like (e.g., format, pagination). For a tool with no annotations, this leaves significant gaps in understanding its behavior.

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

Conciseness5/5

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

The description is very concise and front-loaded: two sentences that directly state the purpose and usage context without any fluff. Every sentence earns its place by adding value, making it efficient and well-structured.

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

Completeness3/5

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

Given the tool's low complexity (2 optional parameters, no output schema, no annotations), the description is minimally adequate. It covers the basic purpose and usage but lacks details on output format, error cases, or integration with sibling tools. Without annotations or output schema, it should do more to be fully complete, but it meets the minimum viable threshold.

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?

The input schema has 100% description coverage, with clear documentation for both parameters ('listId' and 'showCompleted'). The description doesn't add any parameter-specific details beyond what the schema provides, such as examples or constraints. According to the rules, with high schema coverage, the baseline is 3, which is appropriate here.

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: 'View active TODO lists and their progress.' It specifies the verb ('view') and resource ('TODO lists'), and adds scope ('active'). However, it doesn't explicitly differentiate from sibling tools like 'timeline' or 'search_history' that might also involve viewing task-related data, so it doesn't reach the highest score.

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

Usage Guidelines3/5

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

The description provides implied usage with 'Perfect for checking current status,' which suggests when to use it (for status checks). However, it doesn't offer explicit guidance on when to use this tool versus alternatives like 'timeline' or 'search_history,' nor does it mention exclusions or prerequisites, keeping it at a basic level.

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

Related 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/anortham/coa-goldfish-mcp'

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