Skip to main content
Glama

tracker_dashboard

Read-onlyIdempotent

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,
Behavior4/5

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

The description adds valuable behavioral context beyond annotations by specifying the comprehensive data returned (project info, epics with task counts, stats, activity, notes). While annotations cover safety (readOnlyHint=true, destructiveHint=false) and idempotency, the description clarifies the tool's role as a dashboard aggregator, which is not captured in structured fields.

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 front-loaded with the core purpose, followed by specific return details and usage guidance. Every sentence adds value: the first defines the tool, the second enumerates returns, and the third provides context. No wasted words or redundancy.

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

Completeness4/5

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

Given the tool's complexity (aggregating multiple data types) and lack of output schema, the description does well by listing all return components. However, it could improve by mentioning pagination, error cases, or data freshness. Annotations cover safety and idempotency, but the description compensates adequately for the missing output schema.

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 input schema fully documents the single parameter. The description does not add any parameter-specific details beyond what the schema provides, such as explaining when to omit 'project_id' or format requirements. Baseline 3 is appropriate given high schema coverage.

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

Purpose5/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 with specific verbs ('Get a comprehensive project overview') and resources ('project info, all epics with task counts, overall stats, recent activity, and recent notes'). It distinguishes itself from siblings like 'project_list' or 'epic_list' by emphasizing it provides a consolidated dashboard view rather than individual data components.

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

Usage Guidelines5/5

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

The description explicitly states when to use this tool: 'This is the best first tool to call when starting work on a project.' This provides clear guidance on its primary use case, distinguishing it from alternatives like 'project_list' for basic info or 'epic_list' for specific data subsets.

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

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