Skip to main content
Glama

export_json

Export your Things.app database as structured JSON for debugging, backup, or data processing. Control completeness, minimal fields, and formatting with optional parameters.

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

  • The registerExportJsonTool function registers the 'export_json' tool on the MCP server. The handler function (async params => { ... }) executes the export logic: validates macOS, calls exportThingsData(), serializes to JSON (prettified or not), and returns the result 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;
          }
        }
      );
    }
  • The exportThingsData function is the core data extraction logic. It finds the Things SQLite database, queries areas, tags, tasks/projects, task-tag relationships, and checklist items based on filters (includeCompleted, includeTrash, minimal), and returns a structured JSON object with summary statistics.
    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;
    }
  • The Zod schema (exportJsonSchema) defining the input parameters for export_json: includeCompleted (boolean, default false), includeTrash (boolean, default false), minimal (boolean, default false), prettify (boolean, default true).
    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')
    });
  • src/index.ts:13-26 (registration)
    Import and registration of the export_json tool. Line 13 imports registerExportJsonTool from './tools/export-json.js', and line 26 calls registerExportJsonTool(server) to register the tool. Also listed in the server-info resource at line 47.
    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);
  • The findThingsDatabase helper locates the Things SQLite database file on macOS by searching the Group Containers directory for the Things container and ThingsData directory.
    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;
    }
Behavior3/5

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

No annotations are provided, so the description must carry the behavioral burden. It describes an export operation, which is implicitly read-only, but does not explicitly confirm no data modification, rate limits, or authentication needs. The description adds minimal behavioral context beyond the tool name.

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, front-loaded sentence of 14 words with no filler. It efficiently conveys the action, resource, format, and use cases.

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

Completeness3/5

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

Given no output schema, the description lacks detail on the exact structure of the JSON output. It mentions 'structured JSON' but does not specify fields or if the export is full or incremental. For a tool with 4 optional parameters, more context on behavior could be useful.

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?

Input schema coverage is 100%, so parameters are well-documented in the schema. The description does not add any additional meaning or context for the parameters beyond what the schema already provides.

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 verb 'Export', the resource 'complete Things database', and the format 'structured JSON'. It also lists use cases (debugging, backup, data processing), distinguishing it from sibling tools that create or update individual items.

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 gives implied usage (for debugging, backup, data processing) but does not explicitly state when NOT to use this tool or how it compares to siblings like things_summary. No exclusions or alternative recommendations are provided.

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