Skip to main content
Glama

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
NameRequiredDescriptionDefault
project_idNoProject ID (omit if only one project exists)

Implementation Reference

  • 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,
      };
    }
  • 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)' },
        },
      },
    },
  • Mapping of 'tracker_dashboard' to its handler function.
    tracker_dashboard: handleDashboard,

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/spranab/saga-mcp'

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