Skip to main content
Glama

add_omnifocus_task

Create a new OmniFocus task with details like name, notes, due date, flags, and project assignment, ensuring efficient task management and organization.

Instructions

Add a new task to OmniFocus

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
deferDateNoThe defer date of the task in ISO format (YYYY-MM-DD or full ISO date)
dueDateNoThe due date of the task in ISO format (YYYY-MM-DD or full ISO date)
estimatedMinutesNoEstimated time to complete the task, in minutes
flaggedNoWhether the task is flagged or not
nameYesThe name of the task
noteNoAdditional notes for the task
projectNameNoThe name of the project to add the task to (will add to inbox if not specified)
tagsNoTags to assign to the task

Implementation Reference

  • src/server.ts:32-37 (registration)
    Registers the 'add_omnifocus_task' MCP tool using the schema and handler exported from src/tools/definitions/addOmniFocusTask.ts
    server.tool(
      "add_omnifocus_task",
      "Add a new task to OmniFocus",
      addOmniFocusTaskTool.schema.shape,
      addOmniFocusTaskTool.handler
    );
  • Zod schema defining the input parameters for the add_omnifocus_task tool
    export const schema = z.object({
      name: z.string().describe("The name of the task"),
      note: z.string().optional().describe("Additional notes for the task"),
      dueDate: z.string().optional().describe("The due date of the task in ISO format (YYYY-MM-DD or full ISO date)"),
      deferDate: z.string().optional().describe("The defer date of the task in ISO format (YYYY-MM-DD or full ISO date)"),
      flagged: z.boolean().optional().describe("Whether the task is flagged or not"),
      estimatedMinutes: z.number().optional().describe("Estimated time to complete the task, in minutes"),
      tags: z.array(z.string()).optional().describe("Tags to assign to the task"),
      projectName: z.string().optional().describe("The name of the project to add the task to (will add to inbox if not specified)"),
      // Hierarchy support
      parentTaskId: z.string().optional().describe("ID of the parent task (preferred for accuracy)"),
      parentTaskName: z.string().optional().describe("Name of the parent task (used if ID not provided; matched within project or globally if no project)"),
      hierarchyLevel: z.number().int().min(0).optional().describe("Explicit level indicator for ordering in batch workflows (0=root) - ignored in single add")
    });
  • The MCP tool handler for 'add_omnifocus_task'. Validates input via schema, calls the primitive implementation, and formats the success/error response.
    export async function handler(args: z.infer<typeof schema>, extra: RequestHandlerExtra) {
      try {
        // Call the addOmniFocusTask function 
        const result = await addOmniFocusTask(args as AddOmniFocusTaskParams);
        console.error('[add_omnifocus_task] args:', JSON.stringify(args));
        console.error('[add_omnifocus_task] result:', JSON.stringify(result));
        
        if (result.success) {
          // Determine actual placement
          const placement = (result as any).placement as 'parent' | 'project' | 'inbox' | undefined;
          let locationText = '';
          if (placement === 'parent') {
            locationText = 'under the parent task';
          } else if (placement === 'project') {
            locationText = args.projectName ? `in project "${args.projectName}"` : 'in a project';
          } else {
            locationText = 'in your inbox';
          }
    
          const tagText = args.tags && args.tags.length > 0
            ? ` with tags: ${args.tags.join(', ')}`
            : '';
    
          const dueDateText = args.dueDate
            ? ` due on ${new Date(args.dueDate).toLocaleDateString()}`
            : '';
    
          // Warning if parent requested but not used
          let placementWarning = '';
          if ((args.parentTaskId || args.parentTaskName) && placement && placement !== 'parent') {
            placementWarning = `\n⚠️ Parent not found; task created ${placement === 'project' ? 'in project' : 'in inbox'}.`;
          }
    
          return {
            content: [{
              type: "text" as const,
              text: `✅ Task "${args.name}" created successfully ${locationText}${dueDateText}${tagText}.${placementWarning}`
            }]
          };
        } else {
          // Task creation failed
          return {
            content: [{
              type: "text" as const,
              text: `Failed to create task: ${result.error}`
            }],
            isError: true
          };
        }
      } catch (err: unknown) {
        const error = err as Error;
        console.error(`Tool execution error: ${error.message}`);
        return {
          content: [{
            type: "text" as const,
            text: `Error creating task: ${error.message}`
          }],
          isError: true
        };
      }
    } 
  • Core implementation function that generates and executes AppleScript to add task to OmniFocus via osascript, handles temp file, parses JSON result.
    export async function addOmniFocusTask(params: AddOmniFocusTaskParams): Promise<{success: boolean, taskId?: string, error?: string, placement?: 'parent' | 'project' | 'inbox'}> {
      try {
        // Generate AppleScript
        const script = generateAppleScript(params);
        console.error("Executing AppleScript via temp file...");
    
        // Write to a temporary AppleScript file to avoid shell escaping issues
        const tempFile = join(tmpdir(), `omnifocus_add_${Date.now()}.applescript`);
        writeFileSync(tempFile, script, { encoding: 'utf8' });
    
        // Execute AppleScript from file
        const { stdout, stderr } = await execAsync(`osascript ${tempFile}`);
    
        if (stderr) {
          console.error("AppleScript stderr:", stderr);
        }
        
        console.error("AppleScript stdout:", stdout);
        
        // Cleanup temp file
        try { unlinkSync(tempFile); } catch {}
        
        // Parse the result
        try {
          const result = JSON.parse(stdout);
          
          // Return the result
          return {
            success: result.success,
            taskId: result.taskId,
            error: result.error,
            placement: result.placement
          };
        } catch (parseError) {
          console.error("Error parsing AppleScript result:", parseError);
          return {
            success: false,
            error: `Failed to parse result: ${stdout}`
          };
        }
      } catch (error: any) {
        console.error("Error in addOmniFocusTask:", error);
        return {
          success: false,
          error: error?.message || "Unknown error in addOmniFocusTask"
        };
      }
    } 
  • Generates the AppleScript code used to interact with OmniFocus application to create tasks, handle hierarchy (parent/project/inbox), dates, tags, etc.
    function generateAppleScript(params: AddOmniFocusTaskParams): string {
      // Sanitize and prepare parameters for AppleScript
      const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
      const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
      const dueDate = params.dueDate || '';
      const deferDate = params.deferDate || '';
      const flagged = params.flagged === true;
      const estimatedMinutes = params.estimatedMinutes?.toString() || '';
      const tags = params.tags || [];
      const projectName = params.projectName?.replace(/['"\\]/g, '\\$&') || '';
      const parentTaskId = params.parentTaskId?.replace(/['"\\]/g, '\\$&') || '';
      const parentTaskName = params.parentTaskName?.replace(/['"\\]/g, '\\$&') || '';
      
      // Generate date constructions outside tell blocks
      let datePreScript = '';
      let dueDateVar = '';
      let deferDateVar = '';
      
      if (dueDate) {
        dueDateVar = `dueDate${Math.random().toString(36).substr(2, 9)}`;
        datePreScript += createDateOutsideTellBlock(dueDate, dueDateVar) + '\n\n';
      }
      
      if (deferDate) {
        deferDateVar = `deferDate${Math.random().toString(36).substr(2, 9)}`;
        datePreScript += createDateOutsideTellBlock(deferDate, deferDateVar) + '\n\n';
      }
      
      // Construct AppleScript with error handling
      let script = datePreScript + `
      try
        tell application "OmniFocus"
          tell front document
            -- Resolve parent task if provided
            set newTask to missing value
            set parentTask to missing value
            set placement to ""
    
            if "${parentTaskId}" is not "" then
              try
                set parentTask to first flattened task where id = "${parentTaskId}"
              end try
              if parentTask is missing value then
                try
                  set parentTask to first inbox task where id = "${parentTaskId}"
                end try
              end if
              -- If projectName provided, ensure parent is within that project
              if parentTask is not missing value and "${projectName}" is not "" then
                try
                  set pproj to containing project of parentTask
                  if pproj is missing value or name of pproj is not "${projectName}" then set parentTask to missing value
                end try
              end if
            end if
    
            if parentTask is missing value and "${parentTaskName}" is not "" then
              if "${projectName}" is not "" then
                -- Find by name but constrain to the specified project
                try
                  set parentTask to first flattened task where name = "${parentTaskName}"
                end try
                if parentTask is not missing value then
                  try
                    set pproj to containing project of parentTask
                    if pproj is missing value or name of pproj is not "${projectName}" then set parentTask to missing value
                  end try
                end if
              else
                -- No project specified; allow global or inbox match by name
                try
                  set parentTask to first flattened task where name = "${parentTaskName}"
                end try
                if parentTask is missing value then
                  try
                    set parentTask to first inbox task where name = "${parentTaskName}"
                  end try
                end if
              end if
            end if
    
            if parentTask is not missing value then
              -- Create task under parent task
              set newTask to make new task with properties {name:"${name}"} at end of tasks of parentTask
            else if "${projectName}" is not "" then
              -- Create under specified project
              try
                set theProject to first flattened project where name = "${projectName}"
                set newTask to make new task with properties {name:"${name}"} at end of tasks of theProject
              on error
                return "{\\\"success\\\":false,\\\"error\\\":\\\"Project not found: ${projectName}\\\"}"
              end try
            else
              -- Fallback to inbox
              set newTask to make new inbox task with properties {name:"${name}"}
            end if
            
            -- Set task properties
            ${note ? `set note of newTask to "${note}"` : ''}
            ${dueDate ? `
              -- Set due date
              set due date of newTask to ` + dueDateVar : ''}
            ${deferDate ? `
              -- Set defer date
              set defer date of newTask to ` + deferDateVar : ''}
            ${flagged ? `set flagged of newTask to true` : ''}
            ${estimatedMinutes ? `set estimated minutes of newTask to ${estimatedMinutes}` : ''}
            
            -- Derive placement from container; distinguish real parent vs project root task
            try
              set placement to "inbox"
              set ctr to container of newTask
              set cclass to class of ctr as string
              set ctrId to id of ctr as string
              if cclass is "project" then
                set placement to "project"
              else if cclass is "task" then
                if parentTask is not missing value then
                  set parentId to id of parentTask as string
                  if ctrId is equal to parentId then
                    set placement to "parent"
                  else
                    -- Likely the project's root task; treat as project
                    set placement to "project"
                  end if
                else
                  -- No explicit parent requested; container is root task -> treat as project
                  set placement to "project"
                end if
              else
                set placement to "inbox"
              end if
            on error
              -- If container access fails (e.g., inbox), default based on projectName
              if "${projectName}" is not "" then
                set placement to "project"
              else
                set placement to "inbox"
              end if
            end try
    
            -- Get the task ID
            set taskId to id of newTask as string
            
            -- Add tags if provided
            ${tags.length > 0 ? tags.map(tag => {
              const sanitizedTag = tag.replace(/['"\\]/g, '\\$&');
              return `
              try
                set theTag to first flattened tag where name = "${sanitizedTag}"
                add theTag to tags of newTask
              on error
                -- Tag might not exist, try to create it
                try
                  set theTag to make new tag with properties {name:"${sanitizedTag}"}
                  add theTag to tags of newTask
                on error
                  -- Could not create or add tag
                end try
              end try`;
            }).join('\n') : ''}
            
            -- Return success with task ID and placement
            return "{\\\"success\\\":true,\\\"taskId\\\":\\"" & taskId & "\\",\\\"name\\\":\\"${name}\\\",\\\"placement\\\":\\"" & placement & "\\"}"
          end tell
        end tell
      on error errorMessage
        return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
      end try
      `;
      
      return script;
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. While 'Add a new task' implies a write operation, it doesn't specify whether this requires authentication, what happens on success/failure, if there are rate limits, or how errors are handled. For a mutation tool with zero annotation coverage, this 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 a single, efficient sentence that states the core purpose without any wasted words. It's appropriately sized and front-loaded, making it easy for an agent to quickly understand what the tool does.

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?

For a mutation tool with 8 parameters, no annotations, and no output schema, the description is inadequate. It doesn't explain what happens after task creation, what the return value might be, or provide any behavioral context. The description should do more to compensate for the lack of structured metadata.

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%, so the schema fully documents all 8 parameters. The description adds no additional parameter information beyond what's already in the schema. According to scoring rules, when schema coverage is high (>80%), the baseline is 3 even with no param info in the description.

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 action ('Add') and resource ('new task to OmniFocus'), making the purpose immediately understandable. It doesn't differentiate from sibling tools like 'add_project' or 'edit_item', which would require mentioning this is specifically for tasks rather than projects or editing existing items.

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 is provided on when to use this tool versus alternatives like 'add_project', 'edit_item', or 'batch_add_items'. The description doesn't mention prerequisites, context, or any exclusions that would help an agent choose between these sibling tools.

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

Related 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/themotionmachine/OmniFocus-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server