Skip to main content
Glama

export_json

Export your complete Things database as structured JSON for debugging, backup, or data processing with customizable options.

Instructions

Export complete Things database as structured JSON for debugging, backup, or data processing.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
includeCompletedNoInclude completed/canceled tasks and projects in the export (default: false)
includeTrashNoInclude trashed items in the export (default: false). Use with caution as this includes deleted data
minimalNoExport minimal data structure with only essential fields (default: false). Reduces output size for processing
prettifyNoPretty-print JSON with indentation (default: true). Set to false for compact single-line output

Implementation Reference

  • Core handler function that locates the Things database, applies filters based on parameters, executes SQL queries to fetch areas, tags, tasks, projects, checklist items, and task-tag relationships, formats dates, builds summary statistics, and returns structured JSON data.
    function exportThingsData(params: any): any {
      const dbPath = findThingsDatabase();
      logger.info('Exporting Things database', { path: dbPath, params });
      
      // Build status filters
      let statusFilter = 'status = 0 AND trashed = 0'; // Open items only
      if (params.includeCompleted && params.includeTrash) {
        statusFilter = '1=1'; // All items
      } else if (params.includeCompleted) {
        statusFilter = 'trashed = 0'; // All non-trashed
      } else if (params.includeTrash) {
        statusFilter = 'status = 0'; // Open and trashed
      }
      
      const export_data: any = {
        export_info: {
          exported_at: new Date().toISOString(),
          source: 'Things MCP Server',
          database_path: dbPath,
          filters: {
            include_completed: params.includeCompleted,
            include_trash: params.includeTrash,
            minimal: params.minimal
          }
        }
      };
      
      // Export Areas
      const areasData = executeSqlQuery(dbPath, "SELECT uuid, title, visible, index1 FROM TMArea ORDER BY index1");
      export_data.areas = areasData.map(row => {
        const area: any = {
          id: row[0],
          title: row[1] || null,
          visible: row[2] === '1',
          sort_order: parseInt(row[3]) || 0
        };
        
        if (params.minimal) {
          return { id: area.id, title: area.title };
        }
        return area;
      });
      
      // Export Tags
      const tagsData = executeSqlQuery(dbPath, "SELECT uuid, title, shortcut, index1 FROM TMTag ORDER BY index1");
      export_data.tags = tagsData.map(row => {
        const tag: any = {
          id: row[0],
          title: row[1] || null,
          shortcut: row[2] || null,
          sort_order: parseInt(row[3]) || 0
        };
        
        if (params.minimal) {
          return { id: tag.id, title: tag.title };
        }
        return tag;
      });
      
      // Export Tasks and Projects
      const tasksQuery = params.minimal 
        ? `SELECT uuid, title, type, status, trashed, area, project FROM TMTask WHERE ${statusFilter} ORDER BY creationDate`
        : `SELECT uuid, title, notes, type, status, trashed, creationDate, userModificationDate, startDate, deadline, completionDate, area, project, checklistItemsCount, openChecklistItemsCount, index1 FROM TMTask WHERE ${statusFilter} ORDER BY creationDate`;
      
      const tasksData = executeSqlQuery(dbPath, tasksQuery);
      export_data.tasks = tasksData.map(row => {
        if (params.minimal) {
          return {
            id: row[0],
            title: row[1] || null,
            type: parseInt(row[2]) || 0, // 0=task, 1=project, 2=heading
            status: parseInt(row[3]) || 0, // 0=open, 3=completed, 2=canceled
            trashed: row[4] === '1',
            area_id: row[5] || null,
            project_id: row[6] || null
          };
        }
        
        const task: any = {
          id: row[0],
          title: row[1] || null,
          notes: row[2] || null,
          type: parseInt(row[3]) || 0,
          status: parseInt(row[4]) || 0,
          trashed: row[5] === '1',
          creation_date: formatDate(row[6]),
          modification_date: formatDate(row[7]),
          start_date: formatDate(row[8]),
          deadline: formatDate(row[9]),
          completion_date: formatDate(row[10]),
          area_id: row[11] || null,
          project_id: row[12] || null,
          checklist_items_total: parseInt(row[13]) || 0,
          checklist_items_open: parseInt(row[14]) || 0,
          sort_order: parseInt(row[15]) || 0
        };
        
        return task;
      });
      
      // Export Task-Tag relationships
      const taskTagData = executeSqlQuery(dbPath, "SELECT tasks, tags FROM TMTaskTag");
      export_data.task_tags = taskTagData.map(row => ({
        task_id: row[0],
        tag_id: row[1]
      }));
      
      // Export Checklist Items (if not minimal)
      if (!params.minimal) {
        const checklistQuery = `SELECT uuid, title, status, creationDate, task FROM TMChecklistItem WHERE task IN (SELECT uuid FROM TMTask WHERE ${statusFilter}) ORDER BY creationDate`;
        const checklistData = executeSqlQuery(dbPath, checklistQuery);
        export_data.checklist_items = checklistData.map(row => ({
          id: row[0],
          title: row[1] || null,
          status: parseInt(row[2]) || 0, // 0=open, 3=completed
          creation_date: formatDate(row[3]),
          task_id: row[4]
        }));
      }
      
      // Add summary statistics
      export_data.summary = {
        total_areas: export_data.areas.length,
        total_tags: export_data.tags.length,
        total_items: export_data.tasks.length,
        total_tasks: export_data.tasks.filter((t: any) => t.type === 0).length,
        total_projects: export_data.tasks.filter((t: any) => t.type === 1).length,
        total_headings: export_data.tasks.filter((t: any) => t.type === 2).length,
        open_items: export_data.tasks.filter((t: any) => t.status === 0 && !t.trashed).length,
        completed_items: export_data.tasks.filter((t: any) => t.status === 3).length,
        trashed_items: export_data.tasks.filter((t: any) => t.trashed).length
      };
      
      if (!params.minimal) {
        export_data.summary.total_checklist_items = export_data.checklist_items?.length || 0;
        export_data.summary.total_task_tag_relationships = export_data.task_tags.length;
      }
      
      return export_data;
    }
  • Zod schema defining the input parameters for the export_json tool: includeCompleted, includeTrash, minimal, prettify.
    const exportJsonSchema = z.object({
      includeCompleted: z.boolean()
        .optional()
        .default(false)
        .describe('Include completed/canceled tasks and projects in the export (default: false)'),
      includeTrash: z.boolean()
        .optional()
        .default(false)
        .describe('Include trashed items in the export (default: false). Use with caution as this includes deleted data'),
      minimal: z.boolean()
        .optional()
        .default(false)
        .describe('Export minimal data structure with only essential fields (default: false). Reduces output size for processing'),
      prettify: z.boolean()
        .optional()
        .default(true)
        .describe('Pretty-print JSON with indentation (default: true). Set to false for compact single-line output')
    });
  • Registers the 'export_json' tool on the MCP server, providing the name, description, schema, and inline handler that validates platform, calls exportThingsData, stringifies the output, and returns it as text content.
    export function registerExportJsonTool(server: McpServer): void {
      server.tool(
        'export_json',
        'Export complete Things database as structured JSON for debugging, backup, or data processing.',
        exportJsonSchema.shape,
        async (params) => {
          try {
            // Validate macOS platform
            if (process.platform !== 'darwin') {
              throw new Error('Things database access is only available on macOS');
            }
    
            logger.info('Exporting Things database to JSON', { 
              includeCompleted: params.includeCompleted,
              includeTrash: params.includeTrash,
              minimal: params.minimal,
              prettify: params.prettify
            });
            
            const data = exportThingsData(params);
            const jsonOutput = params.prettify ? 
              JSON.stringify(data, null, 2) : 
              JSON.stringify(data);
            
            return {
              content: [{
                type: "text",
                text: jsonOutput
              }]
            };
          } catch (error) {
            logger.error('Failed to export Things database', { 
              error: error instanceof Error ? error.message : error 
            });
            throw error;
          }
        }
      );
    }
  • src/index.ts:13-26 (registration)
    Imports and calls registerExportJsonTool during server initialization to register the export_json tool.
    import { registerExportJsonTool } from './tools/export-json.js';
    
    const server = new McpServer({
      name: 'things-mcp',
      version: '1.0.0'
    });
    
    // Register all tools
    registerAddTodoTool(server);
    registerAddProjectTool(server);
    registerUpdateTodoTool(server);
    registerUpdateProjectTool(server);
    registerThingsSummaryTool(server);
    registerExportJsonTool(server);
  • Helper function to locate the Things.app SQLite database file on macOS by searching standard Library/Group Containers paths.
    function findThingsDatabase(): string {
      const homeDir = process.env.HOME || '/Users/' + process.env.USER;
      const thingsGroupContainer = join(homeDir, 'Library/Group Containers');
      
      if (!existsSync(thingsGroupContainer)) {
        throw new Error('Things group container not found. Please ensure Things.app is installed on macOS.');
      }
      
      const containers = readdirSync(thingsGroupContainer);
      const thingsContainer = containers.find(dir => 
        dir.includes('JLMPQHK86H.com.culturedcode.ThingsMac')
      );
      
      if (!thingsContainer) {
        throw new Error('Things container not found. Please ensure Things.app is installed and has been launched at least once.');
      }
      
      const containerPath = join(thingsGroupContainer, thingsContainer);
      const contents = readdirSync(containerPath);
      const thingsDataDir = contents.find(dir => dir.startsWith('ThingsData-'));
      
      if (!thingsDataDir) {
        throw new Error('ThingsData directory not found.');
      }
      
      const dbPath = join(containerPath, thingsDataDir, 'Things Database.thingsdatabase', 'main.sqlite');
      
      if (!existsSync(dbPath)) {
        throw new Error('Things database file not found.');
      }
      
      return dbPath;
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the tool exports data for specific purposes but lacks details on critical behaviors: whether this is a read-only operation, if it requires special permissions, potential rate limits, or what the output format looks like beyond 'structured JSON'. The description is too vague about the tool's operational characteristics.

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 a single, efficient sentence that front-loads the core action ('Export complete Things database as structured JSON') and follows with use cases. Every word earns its place, with no redundancy or waste, making it highly concise and well-structured.

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

Completeness2/5

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

Given the complexity of a data export tool with no annotations and no output schema, the description is insufficient. It doesn't explain the return values, potential side effects, or error conditions. For a tool that likely produces large JSON outputs, more context on behavior and output structure is needed to be complete.

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 schema fully documents all four parameters with clear descriptions and defaults. The description adds no additional parameter semantics beyond what's in the schema, as it doesn't mention any parameters. This meets the baseline of 3 for high schema coverage without extra value from the description.

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

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Export' and resource 'complete Things database as structured JSON', making the purpose evident. It also provides three use cases (debugging, backup, data processing). However, it doesn't explicitly differentiate from sibling tools like 'things_summary', which might also provide data but in a different format or scope.

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

Usage Guidelines3/5

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

The description implies usage for debugging, backup, or data processing, giving some context on when to use it. However, it doesn't provide explicit guidance on when to choose this tool over alternatives like 'things_summary' or when not to use it (e.g., for small updates vs. full exports). No alternatives are named, and exclusions are not specified.

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/wbopan/things-mcp'

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