add_todo
Add a new to-do to Things.app with a clear title, optional notes, tags, and checklist items. Assign to a project or area, set deadlines, and schedule for specific dates.
Instructions
Create a new to-do item in Things.app. Add notes, tags, checklist items, and assign to projects or areas.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | To-do title (required). Clear, actionable description of the task | |
| notes | No | Additional notes or details for the to-do (max 10,000 characters). Supports markdown formatting for rich text | |
| when | No | Schedule the to-do for a specific time. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date | |
| deadline | No | Set a deadline for the to-do in ISO date format (YYYY-MM-DD). Creates a deadline reminder in Things.app | |
| tags | No | Array of tag names for organizing and categorizing the to-do (max 20 tags). Tags help with filtering and organization | |
| checklistItems | No | Array of checklist item descriptions to add as sub-tasks (max 100 items). Each item becomes a checkable sub-task within the to-do | |
| projectId | No | ID of the project to add this to-do to. Use this when you know the specific project ID | |
| projectName | No | Name of the project to add this to-do to. Things.app will find the project by name and add the to-do there | |
| areaId | No | ID of the area of responsibility to assign this to-do to. Use this when you know the specific area ID | |
| areaName | No | Name of the area of responsibility to assign this to-do to (e.g., "Work", "Personal", "Health") | |
| headingId | No | ID of a specific heading within the target project to organize the to-do under | |
| headingName | No | Name of a heading within the target project to organize the to-do under (e.g., "Phase 1", "Research") | |
| completed | No | Mark the to-do as completed immediately upon creation (default: false). Useful for logging already completed tasks | |
| canceled | No | Mark the to-do as canceled immediately upon creation (default: false). Useful for recording tasks that are no longer needed | |
| creationDate | No | Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for importing historical data | |
| completionDate | No | Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when completed is true |
Implementation Reference
- src/tools/add-todo.ts:61-108 (handler)The registerAddTodoTool function registers the 'add_todo' tool on the MCP server. The handler (lines 66-106) builds Things URL params from the schema input, calls buildThingsUrl('add', urlParams) to construct a things:// URL, opens it via openThingsUrl (which runs 'open "<url>"' on macOS), and returns a success message. It maps all schema fields to Things URL scheme parameters.
export function registerAddTodoTool(server: McpServer): void { server.tool( 'add_todo', 'Create a new to-do item in Things.app. Add notes, tags, checklist items, and assign to projects or areas.', addTodoSchema.shape, async (params) => { try { logger.info('Adding new to-do', { title: params.title }); const urlParams: Record<string, any> = { title: params.title }; // Map schema parameters to Things URL scheme parameters if (params.notes) urlParams.notes = params.notes; if (params.when) urlParams.when = params.when; if (params.deadline) urlParams.deadline = params.deadline; if (params.tags) urlParams.tags = params.tags.join(','); if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(','); if (params.projectId) urlParams['list-id'] = params.projectId; if (params.projectName) urlParams.list = params.projectName; if (params.areaId) urlParams['area-id'] = params.areaId; if (params.areaName) urlParams.area = params.areaName; if (params.headingId) urlParams['heading-id'] = params.headingId; if (params.headingName) urlParams.heading = params.headingName; if (params.completed) urlParams.completed = params.completed; if (params.canceled) urlParams.canceled = params.canceled; if (params.creationDate) urlParams['creation-date'] = params.creationDate; if (params.completionDate) urlParams['completion-date'] = params.completionDate; const url = buildThingsUrl('add', urlParams); logger.debug('Generated URL', { url }); await openThingsUrl(url); return { content: [{ type: "text", text: `Successfully created to-do: ${params.title}` }] }; } catch (error) { logger.error('Failed to add to-do', { error: error instanceof Error ? error.message : error }); throw error; } } ); } - src/tools/add-todo.ts:6-59 (schema)Zod validation schema for add_todo. Defines all input parameters: title (required), notes, when (enum or ISO date), deadline, tags (array up to 20), checklistItems (array up to 100), projectId, projectName, areaId, areaName, headingId, headingName, completed, canceled, creationDate, completionDate.
const addTodoSchema = z.object({ title: z.string().min(1).describe('To-do title (required). Clear, actionable description of the task'), notes: z.string().max(10000).optional().describe('Additional notes or details for the to-do (max 10,000 characters). Supports markdown formatting for rich text'), when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday']) .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/)) .optional() .describe('Schedule the to-do for a specific time. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date'), deadline: z.string() .regex(/^\d{4}-\d{2}-\d{2}$/) .optional() .describe('Set a deadline for the to-do in ISO date format (YYYY-MM-DD). Creates a deadline reminder in Things.app'), tags: z.array(z.string().min(1)) .max(20) .optional() .describe('Array of tag names for organizing and categorizing the to-do (max 20 tags). Tags help with filtering and organization'), checklistItems: z.array(z.string().min(1)) .max(100) .optional() .describe('Array of checklist item descriptions to add as sub-tasks (max 100 items). Each item becomes a checkable sub-task within the to-do'), projectId: z.string() .optional() .describe('ID of the project to add this to-do to. Use this when you know the specific project ID'), projectName: z.string() .optional() .describe('Name of the project to add this to-do to. Things.app will find the project by name and add the to-do there'), areaId: z.string() .optional() .describe('ID of the area of responsibility to assign this to-do to. Use this when you know the specific area ID'), areaName: z.string() .optional() .describe('Name of the area of responsibility to assign this to-do to (e.g., "Work", "Personal", "Health")'), headingId: z.string() .optional() .describe('ID of a specific heading within the target project to organize the to-do under'), headingName: z.string() .optional() .describe('Name of a heading within the target project to organize the to-do under (e.g., "Phase 1", "Research")'), completed: z.boolean() .optional() .default(false) .describe('Mark the to-do as completed immediately upon creation (default: false). Useful for logging already completed tasks'), canceled: z.boolean() .optional() .default(false) .describe('Mark the to-do as canceled immediately upon creation (default: false). Useful for recording tasks that are no longer needed'), creationDate: z.string() .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) .optional() .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for importing historical data'), completionDate: z.string() .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) .optional() .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when completed is true') }); - src/index.ts:8-21 (registration)The 'add_todo' tool is registered by importing registerAddTodoTool from './tools/add-todo.js' and calling it with the MCP server instance at line 21. It's also listed in the server-info resource at line 42.
import { registerAddTodoTool } from './tools/add-todo.js'; import { registerAddProjectTool } from './tools/add-project.js'; import { registerUpdateTodoTool } from './tools/update-todo.js'; import { registerUpdateProjectTool } from './tools/update-project.js'; import { registerThingsSummaryTool } from './tools/things-summary.js'; import { registerExportJsonTool } from './tools/export-json.js'; const server = new McpServer({ name: 'things-mcp', version: '1.0.0' }); // Register all tools registerAddTodoTool(server); - src/utils/url-builder.ts:8-31 (helper)buildThingsUrl constructs a things:// URL for a given command and params. Used by the add_todo handler to build the URL with 'add' command and all mapped parameters.
export function buildThingsUrl(command: ThingsCommand, params: Record<string, any>): string { const baseUrl = `things:///${command}`; const queryParts: string[] = []; for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null && value !== '') { let encodedValue: string; if (key === 'data' && command === 'json') { // JSON data needs to be stringified and encoded encodedValue = encodeURIComponent(JSON.stringify(value)); } else { // Encode all values using proper percent encoding const stringValue = Array.isArray(value) ? value.join(',') : String(value); encodedValue = encodeURIComponent(stringValue); } queryParts.push(`${encodeURIComponent(key)}=${encodedValue}`); } } const queryString = queryParts.join('&'); return queryString ? `${baseUrl}?${queryString}` : baseUrl; } - src/utils/url-builder.ts:33-43 (helper)openThingsUrl opens the Things URL on macOS via the 'open' shell command. Used by the add_todo handler to trigger the URL and create the to-do.
export async function openThingsUrl(url: string): Promise<void> { if (process.platform !== 'darwin') { throw new Error('Things URL scheme is only supported on macOS'); } try { await execAsync(`open "${url}"`); } catch (error) { throw error; } } - src/types/things.ts:17-28 (schema)AddTodoParams TypeScript interface defines the shape of parameters passed to the Things URL scheme when adding a to-do.
export interface AddTodoParams { title: string; notes?: string; when?: WhenValue; deadline?: string; tags?: string; 'checklist-items'?: string; list?: string; heading?: string; completed?: boolean; canceled?: boolean; }