Skip to main content
Glama

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 } } } }

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