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
| Name | Required | Description | Default |
|---|---|---|---|
| includeCompleted | No | Include completed/canceled tasks and projects in the export (default: false) | |
| includeTrash | No | Include trashed items in the export (default: false). Use with caution as this includes deleted data | |
| minimal | No | Export minimal data structure with only essential fields (default: false). Reduces output size for processing | |
| prettify | No | Pretty-print JSON with indentation (default: true). Set to false for compact single-line output |
Implementation Reference
- src/tools/export-json.ts:235-273 (handler)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; } } ); } - src/tools/export-json.ts:94-233 (handler)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; } - src/tools/export-json.ts:8-25 (schema)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); - src/tools/export-json.ts:27-59 (helper)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; }