Skip to main content
Glama

tracker_export

Read-onlyIdempotent

Export complete project data including epics, tasks, dependencies, and comments as nested JSON for backup, migration, or sharing purposes.

Instructions

Export a full project as nested JSON. Includes all epics, tasks, subtasks, comments, dependencies, and related notes. Useful for backup, migration, or sharing.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idNoProject ID to export (omit if only one project exists)

Implementation Reference

  • The handleExport function performs the actual logic for the 'tracker_export' tool, querying the database for project data, epics, tasks, subtasks, comments, dependencies, and notes, and then restructuring them into a nested JSON export format.
    function handleExport(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) throw new Error('No projects found. Create a project first.');
        projectId = first.id;
      }
    
      const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId) as Record<string, unknown>;
      if (!project) throw new Error(`Project ${projectId} not found`);
    
      const epics = db.prepare('SELECT * FROM epics WHERE project_id = ? ORDER BY sort_order, created_at')
        .all(projectId) as Array<Record<string, unknown>>;
    
      const epicData = epics.map((epic) => {
        const tasks = db.prepare('SELECT * FROM tasks WHERE epic_id = ? ORDER BY sort_order, created_at')
          .all(epic.id as number) as Array<Record<string, unknown>>;
    
        const taskData = tasks.map((task) => {
          const taskId = task.id as number;
    
          const subtasks = db.prepare('SELECT * FROM subtasks WHERE task_id = ? ORDER BY sort_order, created_at')
            .all(taskId) as Array<Record<string, unknown>>;
    
          const comments = db.prepare('SELECT * FROM comments WHERE task_id = ? ORDER BY created_at ASC')
            .all(taskId) as Array<Record<string, unknown>>;
    
          const deps = db.prepare('SELECT depends_on_task_id FROM task_dependencies WHERE task_id = ?')
            .all(taskId) as Array<{ depends_on_task_id: number }>;
    
          return {
            _original_id: task.id,
            title: task.title,
            description: task.description,
            status: task.status,
            priority: task.priority,
            sort_order: task.sort_order,
            assigned_to: task.assigned_to,
            estimated_hours: task.estimated_hours,
            actual_hours: task.actual_hours,
            due_date: task.due_date,
            source_ref: task.source_ref,
            tags: task.tags,
            metadata: task.metadata,
            depends_on: deps.map((d) => d.depends_on_task_id),
            subtasks: subtasks.map((s) => ({
              title: s.title,
              status: s.status,
              sort_order: s.sort_order,
            })),
            comments: comments.map((c) => ({
              author: c.author,
              content: c.content,
              created_at: c.created_at,
            })),
          };
        });
    
        return {
          _original_id: epic.id,
          name: epic.name,
          description: epic.description,
          status: epic.status,
          priority: epic.priority,
          sort_order: epic.sort_order,
          tags: epic.tags,
          metadata: epic.metadata,
          tasks: taskData,
        };
      });
    
      // Collect notes linked to this project, its epics, or its tasks
      const notes: Array<Record<string, unknown>> = [];
    
      notes.push(...db.prepare(
        `SELECT * FROM notes WHERE related_entity_type = 'project' AND related_entity_id = ?`
      ).all(projectId) as Array<Record<string, unknown>>);
    
      const epicIds = epics.map((e) => e.id as number);
      if (epicIds.length > 0) {
        const placeholders = epicIds.map(() => '?').join(',');
        notes.push(...db.prepare(
          `SELECT * FROM notes WHERE related_entity_type = 'epic' AND related_entity_id IN (${placeholders})`
        ).all(...epicIds) as Array<Record<string, unknown>>);
      }
    
      const allTaskIds: number[] = [];
      for (const epic of epics) {
        const tasks = db.prepare('SELECT id FROM tasks WHERE epic_id = ?')
          .all(epic.id as number) as Array<{ id: number }>;
        allTaskIds.push(...tasks.map((t) => t.id));
      }
      if (allTaskIds.length > 0) {
        const placeholders = allTaskIds.map(() => '?').join(',');
        notes.push(...db.prepare(
          `SELECT * FROM notes WHERE related_entity_type = 'task' AND related_entity_id IN (${placeholders})`
        ).all(...allTaskIds) as Array<Record<string, unknown>>);
      }
    
      // Include unlinked notes
      notes.push(...db.prepare(
        'SELECT * FROM notes WHERE related_entity_type IS NULL'
      ).all() as Array<Record<string, unknown>>);
    
      const noteData = notes.map((n) => ({
        title: n.title,
        content: n.content,
        note_type: n.note_type,
        related_entity_type: n.related_entity_type,
        _original_related_entity_id: n.related_entity_id,
        tags: n.tags,
        metadata: n.metadata,
      }));
    
      return {
        format_version: '1.1',
        exported_at: new Date().toISOString(),
        project: {
          name: project.name,
          description: project.description,
          status: project.status,
          tags: project.tags,
          metadata: project.metadata,
          epics: epicData,
        },
        notes: noteData,
      };
    }
  • The definition of the 'tracker_export' tool including its name, description, and input schema.
    export const definitions: Tool[] = [
      {
        name: 'tracker_export',
        description:
          'Export a full project as nested JSON. Includes all epics, tasks, subtasks, comments, dependencies, and related notes. Useful for backup, migration, or sharing.',
        annotations: { title: 'Export Project', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
        inputSchema: {
          type: 'object',
          properties: {
            project_id: {
              type: 'integer',
              description: 'Project ID to export (omit if only one project exists)',
            },
          },
        },
      },
  • Registration of the tracker_export handler in the tool handlers mapping.
    export const handlers: Record<string, ToolHandler> = {
      tracker_export: handleExport,
      tracker_import: handleImport,
    };
Behavior3/5

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

Annotations already cover key behavioral traits (readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=false), so the bar is lower. The description adds some context by specifying the nested JSON format and comprehensive data inclusion, but does not disclose additional aspects like rate limits, authentication needs, or file size considerations. No contradiction with annotations exists.

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 appropriately sized and front-loaded, with the first sentence stating the core purpose and the second providing usage context. Every sentence earns its place by adding value without redundancy, making it efficient and easy to parse.

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 (exporting a full project with nested data) and the absence of an output schema, the description is mostly complete by specifying the JSON format and data included. However, it could benefit from mentioning potential limitations (e.g., large exports, error handling) to fully prepare the agent. Annotations provide safety and idempotency info, but the description doesn't fully compensate for the lack of output details.

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%, with the parameter 'project_id' fully documented in the schema. The description does not add any meaning beyond what the schema provides (e.g., it doesn't explain format or constraints for project_id). With high schema coverage, the baseline is 3, and the description meets this by not compensating but not detracting either.

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 specific action ('Export a full project as nested JSON') and resource ('project'), and distinguishes from siblings by specifying the comprehensive data included (epics, tasks, subtasks, comments, dependencies, notes). This is more specific than just 'export' and differentiates from tools like tracker_import or project_list.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool ('Useful for backup, migration, or sharing'), which helps the agent understand appropriate scenarios. However, it does not explicitly state when not to use it or name specific alternatives (e.g., tracker_import for reverse operation, project_list for summaries), keeping it from a perfect score.

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