tracker_dashboard
View project overview including epics, task status, recent activity, and notes to track progress and coordinate work in Saga MCP's structured database.
Instructions
Get a comprehensive project overview in a single call. Returns: project info, all epics with task counts, overall stats (total/done/blocked/in_progress), recent activity, and recent notes. This is the best first tool to call when starting work on a project.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Project ID (omit if only one project exists) |
Implementation Reference
- src/tools/dashboard.ts:34-187 (handler)The implementation of the 'tracker_dashboard' tool, which gathers project stats, epics, blocked tasks, and recent activities.
function handleDashboard(args: Record<string, unknown>) { const db = getDb(); let projectId = args.project_id as number | undefined; if (!projectId) { const first = db.prepare('SELECT id FROM projects LIMIT 1').get() as { id: number } | undefined; if (!first) { return { message: 'No projects found. Use tracker_init or project_create to get started.', projects: [], }; } projectId = first.id; } const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId); if (!project) throw new Error(`Project ${projectId} not found`); // Aggregate stats const stats = db .prepare( ` WITH epic_ids AS ( SELECT id FROM epics WHERE project_id = ? ), task_stats AS ( SELECT COUNT(*) as total_tasks, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as tasks_done, SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as tasks_in_progress, SUM(CASE WHEN status = 'blocked' THEN 1 ELSE 0 END) as tasks_blocked, SUM(CASE WHEN status = 'todo' THEN 1 ELSE 0 END) as tasks_todo, SUM(CASE WHEN status = 'review' THEN 1 ELSE 0 END) as tasks_review, COALESCE(SUM(estimated_hours), 0) as total_estimated_hours, COALESCE(SUM(actual_hours), 0) as total_actual_hours FROM tasks WHERE epic_id IN (SELECT id FROM epic_ids) ) SELECT (SELECT COUNT(*) FROM epic_ids) as total_epics, ts.*, CASE WHEN ts.total_tasks > 0 THEN ROUND(ts.tasks_done * 100.0 / ts.total_tasks, 1) ELSE 0 END as completion_pct FROM task_stats ts ` ) .get(projectId); // Epics with task counts const epics = db .prepare( ` SELECT e.*, COUNT(t.id) as task_count, SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) as done_count, SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) as blocked_count, CASE WHEN COUNT(t.id) > 0 THEN ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id), 1) ELSE 0 END as completion_pct FROM epics e LEFT JOIN tasks t ON t.epic_id = e.id WHERE e.project_id = ? GROUP BY e.id ORDER BY e.sort_order, e.created_at ` ) .all(projectId); // Blocked tasks const blockedTasks = db .prepare( ` SELECT t.id, t.title, t.priority, e.name as epic_name FROM tasks t JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ? AND t.status = 'blocked' ORDER BY CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END ` ) .all(projectId); // Overdue tasks const today = new Date().toISOString().slice(0, 10); const overdueTasks = db .prepare( `SELECT t.id, t.title, t.due_date, t.priority, e.name as epic_name FROM tasks t JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ? AND t.due_date < ? AND t.status NOT IN ('done') ORDER BY t.due_date ASC` ) .all(projectId, today) as Array<Record<string, unknown>>; // Recent activity (last 10) const recentActivity = db .prepare( 'SELECT summary, action, entity_type, entity_id, created_at FROM activity_log ORDER BY created_at DESC LIMIT 10' ) .all(); // Recent notes (last 5) const recentNotes = db .prepare('SELECT id, title, note_type, created_at FROM notes ORDER BY created_at DESC LIMIT 5') .all(); // Generate natural language summary const s = stats as Record<string, number>; const p = project as Record<string, unknown>; const epicList = epics as Array<Record<string, unknown>>; const summaryParts: string[] = []; summaryParts.push(`${p.name}: ${s.total_tasks} tasks across ${s.total_epics} epics. ${s.completion_pct}% complete.`); const activeEpics = epicList.filter((e) => e.status === 'in_progress'); if (activeEpics.length > 0) { const activeStr = activeEpics .map((e) => `${e.name} (${e.done_count}/${e.task_count} done)`) .join(', '); summaryParts.push(`Active: ${activeStr}.`); } const nextEpic = epicList.find((e) => e.status === 'planned'); if (nextEpic) { summaryParts.push(`Next up: ${nextEpic.name} (${nextEpic.task_count} tasks).`); } if (s.tasks_blocked > 0) { summaryParts.push(`${s.tasks_blocked} blocked task(s).`); } else { summaryParts.push('No blocked tasks.'); } if (overdueTasks.length > 0) { summaryParts.push(`${overdueTasks.length} overdue task(s).`); } if (s.tasks_in_progress > 0) { summaryParts.push(`${s.tasks_in_progress} in progress.`); } const summary = summaryParts.join(' '); return { summary, project, stats, epics, blocked_tasks: blockedTasks, overdue_tasks: overdueTasks, recent_activity: recentActivity, recent_notes: recentNotes, }; } - src/tools/dashboard.ts:7-18 (schema)Definition and schema for 'tracker_dashboard'.
{ name: 'tracker_dashboard', description: 'Get a comprehensive project overview in a single call. Returns: project info, all epics with task counts, overall stats (total/done/blocked/in_progress), recent activity, and recent notes. This is the best first tool to call when starting work on a project.', annotations: { title: 'Project Dashboard', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, inputSchema: { type: 'object', properties: { project_id: { type: 'integer', description: 'Project ID (omit if only one project exists)' }, }, }, }, - src/tools/dashboard.ts:223-223 (registration)Mapping of 'tracker_dashboard' to its handler function.
tracker_dashboard: handleDashboard,