Skip to main content
Glama

update_todo

Update an existing to-do in Things.app by modifying its title, notes, schedule, tags, checklist items, project/area assignment, or completion status.

Instructions

Update an existing to-do item in Things.app. Modify title, notes, scheduling, tags, checklist items, project/area assignment, and completion status.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesThe unique ID of the to-do to update. This ID can be obtained from the list_todos tool
titleNoUpdate the to-do title with a new clear, actionable description of the task
notesNoReplace existing notes with new content (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes
prependNotesNoAdd text to the beginning of existing notes without replacing them. Useful for adding updates or new information
appendNotesNoAdd text to the end of existing notes without replacing them. Useful for adding follow-up information or status updates
whenNoReschedule the to-do. 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
deadlineNoUpdate the deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline reminder in Things.app
tagsNoReplace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags
addTagsNoAdd these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing tags
checklistItemsNoReplace all current checklist items with this new set (max 100 items). This completely replaces existing checklist items
prependChecklistItemsNoAdd these checklist items to the beginning of the existing checklist without removing current items
appendChecklistItemsNoAdd these checklist items to the end of the existing checklist without removing current items
projectIdNoMove the to-do to a different project by specifying the project ID
projectNameNoMove the to-do to a different project by specifying the project name. Things.app will find the project by name
areaIdNoMove the to-do to a different area by specifying the area ID
areaNameNoMove the to-do to a different area by specifying the area name (e.g., "Work", "Personal", "Health")
headingIdNoMove the to-do under a specific heading within the target project by heading ID
headingNameNoMove the to-do under a specific heading within the target project by heading name (e.g., "Phase 1", "Research")
completedNoMark the to-do as completed (true) or reopen it (false). Completed to-dos are moved to the Logbook
canceledNoMark the to-do as canceled (true) or restore it (false). Canceled to-dos are moved to the Trash
creationDateNoOverride the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for data migration
completionDateNoSet a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when marking as completed

Implementation Reference

  • Handler function for update_todo tool. Registers the tool with the MCP server, handles both URL scheme updates and JSON completion/cancellation operations.
    export function registerUpdateTodoTool(server: McpServer): void {
      server.tool(
        'update_todo',
        'Update an existing to-do item in Things.app. Modify title, notes, scheduling, tags, checklist items, project/area assignment, and completion status.',
        updateTodoSchema.shape,
        async (params) => {
          try {
            logger.info('Updating to-do', { id: params.id });
            
            const authToken = requireAuthToken();
            
            // Check if we're doing a completion/cancellation operation (use JSON)
            if (params.completed !== undefined || params.canceled !== undefined) {
              const attributes: Record<string, any> = {};
              
              if (params.completed !== undefined) {
                attributes.completed = params.completed;
                if (params.completionDate) {
                  attributes['completion-date'] = params.completionDate;
                }
              }
              
              if (params.canceled !== undefined) {
                attributes.canceled = params.canceled;
              }
              
              const operation: JsonOperation = {
                type: 'to-do',
                operation: 'update',
                id: params.id,
                attributes
              };
              
              await executeJsonOperation(operation, authToken);
            } else {
              // Use URL scheme for other updates
              const urlParams: Record<string, any> = {
                id: params.id,
                'auth-token': authToken
              };
    
              // Map schema parameters to Things URL scheme parameters
              if (params.title) urlParams.title = params.title;
              if (params.notes) urlParams.notes = params.notes;
              if (params.prependNotes) urlParams['prepend-notes'] = params.prependNotes;
              if (params.appendNotes) urlParams['append-notes'] = params.appendNotes;
              if (params.when) urlParams.when = params.when;
              if (params.deadline) urlParams.deadline = params.deadline;
              if (params.tags) urlParams.tags = params.tags.join(',');
              if (params.addTags) urlParams['add-tags'] = params.addTags.join(',');
              if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(',');
              if (params.prependChecklistItems) urlParams['prepend-checklist-items'] = params.prependChecklistItems.join(',');
              if (params.appendChecklistItems) urlParams['append-checklist-items'] = params.appendChecklistItems.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.creationDate) urlParams['creation-date'] = params.creationDate;
              
              const url = buildThingsUrl('update', urlParams);
              logger.debug('Generated URL', { url: url.replace(authToken, '***') });
              
              await openThingsUrl(url);
            }
            
            return {
              content: [{
                type: "text",
                text: `Successfully updated to-do: ${params.id}`
              }]
            };
          } catch (error) {
            logger.error('Failed to update to-do', { error: error instanceof Error ? error.message : error });
            throw error;
          }
        }
      );
    }
  • Zod schema defining all input parameters for update_todo tool, including validation constraints like string lengths, regex patterns, and array size limits.
    const updateTodoSchema = z.object({
      id: z.string().min(1).describe('The unique ID of the to-do to update. This ID can be obtained from the list_todos tool'),
      title: z.string().min(1).optional().describe('Update the to-do title with a new clear, actionable description of the task'),
      notes: z.string().max(10000).optional().describe('Replace existing notes with new content (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes'),
      prependNotes: z.string().optional().describe('Add text to the beginning of existing notes without replacing them. Useful for adding updates or new information'),
      appendNotes: z.string().optional().describe('Add text to the end of existing notes without replacing them. Useful for adding follow-up information or status updates'),
      when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
        .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
        .optional()
        .describe('Reschedule the to-do. 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('Update the deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline reminder in Things.app'),
      tags: z.array(z.string().min(1))
        .max(20)
        .optional()
        .describe('Replace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags'),
      addTags: z.array(z.string().min(1))
        .max(20)
        .optional()
        .describe('Add these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing tags'),
      checklistItems: z.array(z.string().min(1))
        .max(100)
        .optional()
        .describe('Replace all current checklist items with this new set (max 100 items). This completely replaces existing checklist items'),
      prependChecklistItems: z.array(z.string().min(1))
        .optional()
        .describe('Add these checklist items to the beginning of the existing checklist without removing current items'),
      appendChecklistItems: z.array(z.string().min(1))
        .optional()
        .describe('Add these checklist items to the end of the existing checklist without removing current items'),
      projectId: z.string()
        .optional()
        .describe('Move the to-do to a different project by specifying the project ID'),
      projectName: z.string()
        .optional()
        .describe('Move the to-do to a different project by specifying the project name. Things.app will find the project by name'),
      areaId: z.string()
        .optional()
        .describe('Move the to-do to a different area by specifying the area ID'),
      areaName: z.string()
        .optional()
        .describe('Move the to-do to a different area by specifying the area name (e.g., "Work", "Personal", "Health")'),
      headingId: z.string()
        .optional()
        .describe('Move the to-do under a specific heading within the target project by heading ID'),
      headingName: z.string()
        .optional()
        .describe('Move the to-do under a specific heading within the target project by heading name (e.g., "Phase 1", "Research")'),
      completed: z.boolean()
        .optional()
        .describe('Mark the to-do as completed (true) or reopen it (false). Completed to-dos are moved to the Logbook'),
      canceled: z.boolean()
        .optional()
        .describe('Mark the to-do as canceled (true) or restore it (false). Canceled to-dos are moved to the Trash'),
      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 data migration'),
      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 marking as completed')
    });
  • src/index.ts:10-23 (registration)
    Registration of update_todo tool: imports the registration function and calls it with the server instance.
    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);
    registerAddProjectTool(server);
    registerUpdateTodoTool(server);
  • TypeScript interface for UpdateTodoParams used in URL scheme construction for Things.app updates.
    export interface UpdateTodoParams {
      id: string;
      'auth-token': string;
      title?: string;
      notes?: string;
      when?: WhenValue;
      deadline?: string;
      tags?: string;
      'checklist-items'?: string;
      list?: string;
      heading?: string;
      completed?: boolean;
      canceled?: boolean;
      'prepend-notes'?: string;
      'append-notes'?: string;
      'add-tags'?: string;
      'add-checklist-items'?: string;
    }
Behavior2/5

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

With no annotations, the description carries full burden for behavioral disclosure. It only states 'Update' and lists fields, omitting side effects (e.g., replacement vs. append behaviors, authorization needs, or data persistence). The schema descriptions cover specifics but the tool description itself lacks behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence efficiently conveying the tool's purpose. It is concise but could benefit from more structure (e.g., bullet points) for readability.

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 high parameter count (22), no output schema, and no annotations, the description is insufficiently complete. It lacks information about return values, error handling, or important behavioral nuances (e.g., idempotency, constraints).

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?

The input schema has 100% description coverage, so the schema already documents each parameter in detail. The tool description adds only a high-level list of categories, providing marginal value beyond the schema.

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 'Update' and the resource 'existing to-do item in Things.app', listing modifiable fields. However, it does not differentiate from sibling tools like add_todo or update_project, which have overlapping scopes.

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 use when modifying an existing to-do, but provides no explicit guidance on when not to use it or alternatives like add_todo for new items or update_project for project-level changes.

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