get_tasks_by_tag
Retrieve tasks from OmniFocus by specific tags like @home or @work, with options to hide completed tasks or match tag names exactly. Simplify tag-based task filtering for better organization.
Instructions
Get tasks filtered by OmniFocus tags (labels like @home, @work, @urgent). Use this for tag-based filtering, NOT for custom perspective names. Tags are labels assigned to individual tasks.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| exactMatch | No | Set to true for exact tag name match, false for partial (default: false) | |
| hideCompleted | No | Set to false to show completed tasks with this tag (default: true) | |
| tagName | Yes | Name of the tag to filter tasks by |
Implementation Reference
- The MCP tool handler function that validates input via zod, calls the primitive getTasksByTag function, and returns formatted MCP content or error response.
export async function handler(args: z.infer<typeof schema>, extra: RequestHandlerExtra) { try { const result = await getTasksByTag({ tagName: args.tagName, hideCompleted: args.hideCompleted !== false, // Default to true exactMatch: args.exactMatch || false }); return { content: [{ type: "text" as const, text: result }] }; } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; return { content: [{ type: "text" as const, text: `Error getting tasks by tag: ${errorMessage}` }], isError: true }; } } - Zod schema defining the input parameters for the get_tasks_by_tag tool: tagName (required), hideCompleted and exactMatch (optional).
export const schema = z.object({ tagName: z.string().describe("Name of the tag to filter tasks by"), hideCompleted: z.boolean().optional().describe("Set to false to show completed tasks with this tag (default: true)"), exactMatch: z.boolean().optional().describe("Set to true for exact tag name match, false for partial (default: false)") }); - src/server.ts:120-125 (registration)Registers the 'get_tasks_by_tag' tool on the MCP server, providing the tool name, description, input schema, and handler function.
server.tool( "get_tasks_by_tag", "Get tasks filtered by OmniFocus tags (labels like @home, @work, @urgent). Use this for tag-based filtering, NOT for custom perspective names. Tags are labels assigned to individual tasks.", getTasksByTagTool.schema.shape, getTasksByTagTool.handler ); - Core helper function that executes the OmniFocus AppleScript for tag-based task retrieval, processes the result, formats it into a structured markdown output grouped by projects, and handles errors.
export async function getTasksByTag(options: GetTasksByTagOptions): Promise<string> { const { tagName, hideCompleted = true, exactMatch = false } = options; if (!tagName || tagName.trim() === '') { throw new Error('Tag name is required'); } try { // Execute the tasks by tag script const result = await executeOmniFocusScript('@tasksByTag.js', { tagName: tagName.trim(), hideCompleted: hideCompleted, exactMatch: exactMatch }); if (typeof result === 'string') { return result; } // If result is an object, format it if (result && typeof result === 'object') { const data = result as any; if (data.error) { throw new Error(data.error); } // Format the tasks by tag const searchType = exactMatch ? 'exact match' : 'partial match'; let output = `# 🏷 TASKS WITH TAG: "${tagName}" (${searchType})\n\n`; if (data.matchedTags && data.matchedTags.length > 0) { output += `**Matched tags**: ${data.matchedTags.join(', ')}\n\n`; } if (data.tasks && Array.isArray(data.tasks)) { if (data.tasks.length === 0) { output += `No tasks found with tag "${tagName}"\n`; if (data.availableTags && data.availableTags.length > 0) { output += `\n**Available tags**: ${data.availableTags.slice(0, 10).join(', ')}`; if (data.availableTags.length > 10) { output += ` ... and ${data.availableTags.length - 10} more`; } output += '\n'; } } else { const taskCount = data.tasks.length; output += `Found ${taskCount} task${taskCount === 1 ? '' : 's'}:\n\n`; // Group tasks by project for better organization const tasksByProject = new Map<string, any[]>(); data.tasks.forEach((task: any) => { const projectName = task.projectName || '📥 Inbox'; if (!tasksByProject.has(projectName)) { tasksByProject.set(projectName, []); } tasksByProject.get(projectName)!.push(task); }); // Display tasks grouped by project tasksByProject.forEach((tasks, projectName) => { if (tasksByProject.size > 1) { output += `## 📁 ${projectName}\n`; } tasks.forEach((task: any) => { const flagSymbol = task.flagged ? '🚩 ' : ''; const dueDateStr = task.dueDate ? ` [DUE: ${new Date(task.dueDate).toLocaleDateString()}]` : ''; const deferDateStr = task.deferDate ? ` [DEFER: ${new Date(task.deferDate).toLocaleDateString()}]` : ''; const statusStr = task.taskStatus !== 'Available' ? ` (${task.taskStatus})` : ''; const estimateStr = task.estimatedMinutes ? ` ⏱${task.estimatedMinutes}m` : ''; output += `• ${flagSymbol}${task.name}${dueDateStr}${deferDateStr}${statusStr}${estimateStr}\n`; if (task.note && task.note.trim()) { output += ` 📝 ${task.note.trim()}\n`; } // Show all tags for this task if (task.tags && task.tags.length > 0) { const tagNames = task.tags.map((tag: any) => { // Highlight the matched tag return data.matchedTags && data.matchedTags.includes(tag.name) ? `**${tag.name}**` : tag.name; }).join(', '); output += ` 🏷 ${tagNames}\n`; } output += '\n'; }); }); // Summary output += `📊 **Summary**: ${taskCount} task${taskCount === 1 ? '' : 's'} with tag "${tagName}"\n`; } } else { output += "No tasks data available\n"; } return output; } return "Unexpected result format from OmniFocus"; } catch (error) { console.error("Error in getTasksByTag:", error); throw new Error(`Failed to get tasks by tag: ${error instanceof Error ? error.message : 'Unknown error'}`); } }