Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
titleYesTo-do title (required). Clear, actionable description of the task
notesNoAdditional notes or details for the to-do (max 10,000 characters). Supports markdown formatting for rich text
whenNoSchedule 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
deadlineNoSet a deadline for the to-do in ISO date format (YYYY-MM-DD). Creates a deadline reminder in Things.app
tagsNoArray of tag names for organizing and categorizing the to-do (max 20 tags). Tags help with filtering and organization
checklistItemsNoArray of checklist item descriptions to add as sub-tasks (max 100 items). Each item becomes a checkable sub-task within the to-do
projectIdNoID of the project to add this to-do to. Use this when you know the specific project ID
projectNameNoName of the project to add this to-do to. Things.app will find the project by name and add the to-do there
areaIdNoID of the area of responsibility to assign this to-do to. Use this when you know the specific area ID
areaNameNoName of the area of responsibility to assign this to-do to (e.g., "Work", "Personal", "Health")
headingIdNoID of a specific heading within the target project to organize the to-do under
headingNameNoName of a heading within the target project to organize the to-do under (e.g., "Phase 1", "Research")
completedNoMark the to-do as completed immediately upon creation (default: false). Useful for logging already completed tasks
canceledNoMark the to-do as canceled immediately upon creation (default: false). Useful for recording tasks that are no longer needed
creationDateNoOverride the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for importing historical data
completionDateNoSet a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when completed is true

Implementation Reference

  • 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;
          }
        }
      );
    }
  • 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);
  • 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;
    }
  • 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;
      }
    }
  • 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;
    }
Behavior2/5

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

With no annotations provided, the description should disclose behavioral traits. It mentions creating a to-do but does not address side effects, required permissions, rate limits, or what happens on failure. The lack of annotations places full burden on description, which is insufficient.

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 two short, front-loaded sentences with no fluff. Every part adds value.

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?

With 16 parameters and no output schema, the description should cover return values and error cases. It only lists features without explaining what is returned (e.g., created to-do ID) or potential errors.

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 detailed parameter descriptions. The tool description adds minimal extra meaning beyond summarizing the parameter types. Baseline of 3 is appropriate.

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 it creates a new to-do item in Things.app and lists key features (notes, tags, checklists, project/area assignment). This distinguishes it from sibling tools like update_todo or add_project.

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

Usage Guidelines2/5

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

No guidance on when to use this tool versus alternatives such as update_todo or add_project. The description simply says what it does without context for appropriate usage.

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