get_custom_perspective_tasks
Retrieve tasks from a specific OmniFocus custom perspective by name, showing hierarchical task relationships or flat views. Exclude or include completed tasks as needed for focused task management.
Instructions
Get tasks from a specific OmniFocus custom perspective by name. Use this when user refers to perspective names like '今日工作安排', '今日复盘', '本周项目' etc. - these are custom views created in OmniFocus, NOT tags. Supports hierarchical tree display of task relationships.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hideCompleted | No | Whether to hide completed tasks. Set to false to show all tasks including completed ones (default: true) | |
| limit | No | Maximum number of tasks to return in flat view mode (default: 1000, ignored in hierarchy mode) | |
| perspectiveName | Yes | Exact name of the OmniFocus custom perspective (e.g., '今日工作安排', '今日复盘', '本周项目'). This is NOT a tag name. | |
| showHierarchy | No | Display tasks in hierarchical tree structure showing parent-child relationships. Use this when user wants '层级显示' or 'tree view' (default: false) |
Implementation Reference
- MCP tool handler: wrapper that calls the primitive getCustomPerspectiveTasks with processed args and returns MCP-formatted text content or error.export async function handler(args: z.infer<typeof schema>, extra: RequestHandlerExtra) { try { const result = await getCustomPerspectiveTasks({ perspectiveName: args.perspectiveName, hideCompleted: args.hideCompleted !== false, // Default to true limit: args.limit || 1000, showHierarchy: args.showHierarchy || false // Default to 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 custom perspective tasks: ${errorMessage}` }], isError: true }; } }
- Zod schema defining input parameters for the tool: perspectiveName (required), hideCompleted, limit, showHierarchy.export const schema = z.object({ perspectiveName: z.string().describe("Exact name of the OmniFocus custom perspective (e.g., '今日工作安排', '今日复盘', '本周项目'). This is NOT a tag name."), hideCompleted: z.boolean().optional().describe("Whether to hide completed tasks. Set to false to show all tasks including completed ones (default: true)"), limit: z.number().optional().describe("Maximum number of tasks to return in flat view mode (default: 1000, ignored in hierarchy mode)"), showHierarchy: z.boolean().optional().describe("Display tasks in hierarchical tree structure showing parent-child relationships. Use this when user wants '层级显示' or 'tree view' (default: false)") });
- src/server.ts:143-148 (registration)Tool registration in MCP server using name, description, schema.shape, and handler from definitions module.server.tool( "get_custom_perspective_tasks", "Get tasks from a specific OmniFocus custom perspective by name. Use this when user refers to perspective names like '今日工作安排', '今日复盘', '本周项目' etc. - these are custom views created in OmniFocus, NOT tags. Supports hierarchical tree display of task relationships.", getCustomPerspectiveTasksTool.schema.shape, getCustomPerspectiveTasksTool.handler );
- Core primitive function: executes OmniFocus script, parses results, filters tasks, and formats output as hierarchical tree or flat list.export async function getCustomPerspectiveTasks(options: GetCustomPerspectiveTasksOptions): Promise<string> { const { perspectiveName, hideCompleted = true, limit = 1000, showHierarchy = false } = options; if (!perspectiveName) { return "❌ **错误**: 透视名称不能为空"; } try { // Execute the get custom perspective tasks script const result = await executeOmniFocusScript('@getCustomPerspectiveTasks.js', { perspectiveName: perspectiveName }); // 处理各种可能的返回类型(避免之前的错误) let data: any; if (typeof result === 'string') { try { data = JSON.parse(result); } catch (parseError) { throw new Error(`解析字符串结果失败: ${result}`); } } else if (typeof result === 'object' && result !== null) { data = result; } else { throw new Error(`脚本执行返回了无效的结果类型: ${typeof result}, 值: ${result}`); } // 检查是否有错误 if (!data.success) { throw new Error(data.error || 'Unknown error occurred'); } // 处理taskMap数据(新的层级结构) const taskMap = data.taskMap || {}; const allTasks = Object.values(taskMap); // 过滤已完成任务(如果需要) let filteredTasks = allTasks; if (hideCompleted) { filteredTasks = allTasks.filter((task: any) => !task.completed); } if (filteredTasks.length === 0) { return `**透视任务:${perspectiveName}**\n\n暂无${hideCompleted ? '未完成' : ''}任务。`; } // 根据是否显示层级关系选择不同的输出格式 if (showHierarchy) { return formatHierarchicalTasks(perspectiveName, taskMap, hideCompleted); } else { return formatFlatTasks(perspectiveName, filteredTasks, limit, data.count); } } catch (error) { console.error('Error in getCustomPerspectiveTasks:', error); return `❌ **错误**: ${error instanceof Error ? error.message : String(error)}`; } }
- OmniFocus AppleScript/JS: switches to custom perspective, traverses content tree, collects tasks with parent-child relationships into taskMap, returns JSON.// 通过自定义透视名称获取任务(支持层级关系) // 基于用户提供的优秀代码改进 (() => { try { // 获取注入的参数 const perspectiveName = injectedArgs && injectedArgs.perspectiveName ? injectedArgs.perspectiveName : null; if (!perspectiveName) { throw new Error("透视名称不能为空"); } // 通过名称获取自定义透视 let perspective = Perspective.Custom.byName(perspectiveName); if (!perspective) { throw new Error(`未找到名为 "${perspectiveName}" 的自定义透视`); } // 切换到指定透视 document.windows[0].perspective = perspective; // 用于存储所有任务,key为任务ID(支持层级关系) let taskMap = {}; // 遍历内容树,收集任务信息(含层级关系) let rootNode = document.windows[0].content.rootNode; function collectTasks(node, parentId) { if (node.object && node.object instanceof Task) { let t = node.object; let id = t.id.primaryKey; // 记录任务信息(包含层级关系) taskMap[id] = { id: id, name: t.name, note: t.note || "", project: t.project ? t.project.name : null, tags: t.tags ? t.tags.map(tag => tag.name) : [], dueDate: t.dueDate ? t.dueDate.toISOString() : null, deferDate: t.deferDate ? t.deferDate.toISOString() : null, completed: t.completed, flagged: t.flagged, estimatedMinutes: t.estimatedMinutes || null, repetitionRule: t.repetitionRule ? t.repetitionRule.toString() : null, creationDate: t.added ? t.added.toISOString() : null, completionDate: t.completedDate ? t.completedDate.toISOString() : null, parent: parentId, // 父任务ID children: [], // 子任务ID列表,后面补充 }; // 递归收集子任务 node.children.forEach(childNode => { if (childNode.object && childNode.object instanceof Task) { let childId = childNode.object.id.primaryKey; taskMap[id].children.push(childId); collectTasks(childNode, id); } else { collectTasks(childNode, id); } }); } else { // 不是任务节点,递归子节点 node.children.forEach(childNode => collectTasks(childNode, parentId)); } } // 开始收集任务(根任务的parent为null) if (rootNode && rootNode.children) { rootNode.children.forEach(node => collectTasks(node, null)); } // 计算任务总数 const taskCount = Object.keys(taskMap).length; // 返回结果(包含层级结构) const result = { success: true, perspectiveName: perspectiveName, perspectiveId: perspective.identifier, count: taskCount, taskMap: taskMap }; return JSON.stringify(result); } catch (error) { // 错误处理 const errorResult = { success: false, error: error.message || String(error), perspectiveName: perspectiveName || null, perspectiveId: null, count: 0, taskMap: {} }; return JSON.stringify(errorResult); } })();