Skip to main content
Glama

OmniFocus-MCP

addOmniFocusTask.ts9.25 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import { writeFileSync, unlinkSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { createDateOutsideTellBlock } from '../../utils/dateFormatting.js'; const execAsync = promisify(exec); // Interface for task creation parameters export interface AddOmniFocusTaskParams { name: string; note?: string; dueDate?: string; // ISO date string deferDate?: string; // ISO date string flagged?: boolean; estimatedMinutes?: number; tags?: string[]; // Tag names projectName?: string; // Project name to add task to // Hierarchy support parentTaskId?: string; parentTaskName?: string; hierarchyLevel?: number; // ignored for single add } /** * Generate pure AppleScript for task creation */ 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; } /** * Add a task to OmniFocus */ 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" }; } }

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