Skip to main content
Glama

dump_database

Exports the current state of your OmniFocus database, allowing customization to show completed tasks or filter recurring task duplicates.

Instructions

Gets the current state of your OmniFocus database

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hideCompletedNoSet to false to show completed and dropped tasks (default: true)
hideRecurringDuplicatesNoSet to true to hide duplicate instances of recurring tasks (default: true)

Implementation Reference

  • MCP tool handler for 'dump_database' that fetches the OmniFocus database using dumpDatabase() and formats it into a compact text report with options to hide completed tasks and recurring duplicates.
    export async function handler(args: z.infer<typeof schema>, extra: RequestHandlerExtra) {
      try {
        // Get raw database
        const database = await dumpDatabase();
    
        // Format as compact report
        const formattedReport = formatCompactReport(database, {
          hideCompleted: args.hideCompleted !== false, // Default to true
          hideRecurringDuplicates: args.hideRecurringDuplicates !== false // Default to true
        });
    
        return {
          content: [{
            type: "text" as const,
            text: formattedReport
          }]
        };
      } catch (err: unknown) {
        return {
          content: [{
            type: "text" as const,
            text: `Error generating report. Please ensure OmniFocus is running and try again.`
          }],
          isError: true
        };
      }
    }
  • Zod schema defining optional parameters for the dump_database tool: hideCompleted and hideRecurringDuplicates.
    export const schema = z.object({
      hideCompleted: z.boolean().optional().describe("Set to false to show completed and dropped tasks (default: true)"),
      hideRecurringDuplicates: z.boolean().optional().describe("Set to true to hide duplicate instances of recurring tasks (default: true)")
    });
  • src/server.ts:25-30 (registration)
    Registration of the 'dump_database' tool in the MCP server, specifying name, description, schema, and handler.
    server.tool(
      "dump_database",
      "Gets the current state of your OmniFocus database",
      dumpDatabaseTool.schema.shape,
      dumpDatabaseTool.handler
    );
  • Core helper function that executes an OmniFocus AppleScript to dump the full database (tasks, projects, folders, tags) and processes it into a structured OmnifocusDatabase object. Called by the tool handler.
    export async function dumpDatabase(): Promise<OmnifocusDatabase> {
      
      try {
        // Execute the OmniFocus script
        const data = await executeOmniFocusScript('@omnifocusDump.js') as OmnifocusDumpData;
        // wait 1 second
        await new Promise(resolve => setTimeout(resolve, 1000));
     
        // Create an empty database if no data returned
        if (!data) {
          return {
            exportDate: new Date().toISOString(),
            tasks: [],
            projects: {},
            folders: {},
            tags: {}
          };
        }
        
        // Initialize the database object
        const database: OmnifocusDatabase = {
          exportDate: data.exportDate,
          tasks: [],
          projects: {},
          folders: {},
          tags: {}
        };
        
        // Process tasks
        if (data.tasks && Array.isArray(data.tasks)) {
          // Convert the tasks to our OmnifocusTask format
          database.tasks = data.tasks.map((task: OmnifocusDumpTask) => {
            // Get tag names from the tag IDs
            const tagNames = (task.tags || []).map(tagId => {
              return data.tags[tagId]?.name || 'Unknown Tag';
            });
            
            return {
              id: String(task.id),
              name: String(task.name),
              note: String(task.note || ""),
              flagged: Boolean(task.flagged),
              completed: task.taskStatus === "Completed",
              completionDate: null, // Not available in the new format
              dropDate: null, // Not available in the new format
              taskStatus: String(task.taskStatus),
              active: task.taskStatus !== "Completed" && task.taskStatus !== "Dropped",
              dueDate: task.dueDate,
              deferDate: task.deferDate,
              estimatedMinutes: task.estimatedMinutes ? Number(task.estimatedMinutes) : null,
              tags: task.tags || [],
              tagNames: tagNames,
              parentId: task.parentTaskID || null,
              containingProjectId: task.projectID || null,
              projectId: task.projectID || null,
              childIds: task.children || [],
              hasChildren: (task.children && task.children.length > 0) || false,
              sequential: Boolean(task.sequential),
              completedByChildren: Boolean(task.completedByChildren),
              isRepeating: false, // Not available in the new format
              repetitionMethod: null, // Not available in the new format 
              repetitionRule: null, // Not available in the new format
              attachments: [], // Default empty array
              linkedFileURLs: [], // Default empty array
              notifications: [], // Default empty array
              shouldUseFloatingTimeZone: false // Default value
            };
          });
        }
        
        // Process projects
        if (data.projects) {
          for (const [id, project] of Object.entries(data.projects)) {
            database.projects[id] = {
              id: String(project.id),
              name: String(project.name),
              status: String(project.status),
              folderID: project.folderID || null,
              sequential: Boolean(project.sequential),
              effectiveDueDate: project.effectiveDueDate,
              effectiveDeferDate: project.effectiveDeferDate,
              dueDate: project.dueDate,
              deferDate: project.deferDate,
              completedByChildren: Boolean(project.completedByChildren),
              containsSingletonActions: Boolean(project.containsSingletonActions),
              note: String(project.note || ""),
              tasks: project.tasks || [],
              flagged: false, // Default value
              estimatedMinutes: null // Default value
            };
          }
        }
        
        // Process folders
        if (data.folders) {
          for (const [id, folder] of Object.entries(data.folders)) {
            database.folders[id] = {
              id: String(folder.id),
              name: String(folder.name),
              parentFolderID: folder.parentFolderID || null,
              status: String(folder.status),
              projects: folder.projects || [],
              subfolders: folder.subfolders || []
            };
          }
        }
        
        // Process tags
        if (data.tags) {
          for (const [id, tag] of Object.entries(data.tags)) {
            database.tags[id] = {
              id: String(tag.id),
              name: String(tag.name),
              parentTagID: tag.parentTagID || null,
              active: Boolean(tag.active),
              allowsNextAction: Boolean(tag.allowsNextAction),
              tasks: tag.tasks || []
            };
          }
        }
        
        return database;
      } catch (error) {
        console.error("Error in dumpDatabase:", error);
        throw error;
      }
    }
  • Helper function called by the handler to format the raw database into a human-readable compact report with hierarchy, legends, filtering options, and abbreviated tags.
    function formatCompactReport(database: any, options: { hideCompleted: boolean, hideRecurringDuplicates: boolean }): string {
      const { hideCompleted, hideRecurringDuplicates } = options;
    
      // Get current date for the header
      const today = new Date();
      const dateStr = today.toISOString().split('T')[0];
    
      let output = `# OMNIFOCUS [${dateStr}]\n\n`;
    
      // Add legend
      output += `FORMAT LEGEND:
    F: Folder | P: Project | •: Task | 🚩: Flagged
    Dates: [M/D] | Duration: (30m) or (2h) | Tags: <tag1,tag2>
    Status: #next #avail #block #due #over #compl #drop\n\n`;
    
      // Map of folder IDs to folder objects for quick lookup
      const folderMap = new Map();
      Object.values(database.folders).forEach((folder: any) => {
        folderMap.set(folder.id, folder);
      });
    
      // Get all tag names to compute minimum unique prefixes
      const allTagNames = Object.values(database.tags).map((tag: any) => tag.name);
      const tagPrefixMap = computeMinimumUniquePrefixes(allTagNames);
    
      // Function to get folder hierarchy path
      function getFolderPath(folderId: string): string[] {
        const path = [];
        let currentId = folderId;
    
        while (currentId) {
          const folder = folderMap.get(currentId);
          if (!folder) break;
    
          path.unshift(folder.name);
          currentId = folder.parentFolderID;
        }
    
        return path;
      }
    
      // Get root folders (no parent)
      const rootFolders = Object.values(database.folders).filter((folder: any) => !folder.parentFolderID);
    
      // Process folders recursively
      function processFolder(folder: any, level: number): string {
        const indent = '   '.repeat(level);
        let folderOutput = `${indent}F: ${folder.name}\n`;
    
        // Process subfolders
        if (folder.subfolders && folder.subfolders.length > 0) {
          for (const subfolderId of folder.subfolders) {
            const subfolder = database.folders[subfolderId];
            if (subfolder) {
              folderOutput += `${processFolder(subfolder, level + 1)}`;
            }
          }
        }
    
        // Process projects in this folder
        if (folder.projects && folder.projects.length > 0) {
          for (const projectId of folder.projects) {
            const project = database.projects[projectId];
            if (project) {
              folderOutput += processProject(project, level + 1);
            }
          }
        }
    
        return folderOutput;
      }
    
      // Process a project
      function processProject(project: any, level: number): string {
        const indent = '   '.repeat(level);
    
        // Skip if it's completed or dropped and we're hiding completed items
        if (hideCompleted && (project.status === 'Done' || project.status === 'Dropped')) {
          return '';
        }
    
        // Format project status info
        let statusInfo = '';
        if (project.status === 'OnHold') {
          statusInfo = ' [OnHold]';
        } else if (project.status === 'Dropped') {
          statusInfo = ' [Dropped]';
        }
    
        // Add due date if present
        if (project.dueDate) {
          const dueDateStr = formatCompactDate(project.dueDate);
          statusInfo += statusInfo ? ` [DUE:${dueDateStr}]` : ` [DUE:${dueDateStr}]`;
        }
    
        // Add flag if present
        const flaggedSymbol = project.flagged ? ' 🚩' : '';
    
        let projectOutput = `${indent}P: ${project.name}${flaggedSymbol}${statusInfo}\n`;
    
        // Process tasks in this project
        const projectTasks = database.tasks.filter((task: any) =>
          task.projectId === project.id && !task.parentId
        );
    
        if (projectTasks.length > 0) {
          for (const task of projectTasks) {
            projectOutput += processTask(task, level + 1);
          }
        }
    
        return projectOutput;
      }
    
      // Process a task
      function processTask(task: any, level: number): string {
        const indent = '   '.repeat(level);
    
        // Skip if it's completed or dropped and we're hiding completed items
        if (hideCompleted && (task.completed || task.taskStatus === 'Completed' || task.taskStatus === 'Dropped')) {
          return '';
        }
    
        // Flag symbol
        const flagSymbol = task.flagged ? '🚩 ' : '';
    
        // Format dates
        let dateInfo = '';
        if (task.dueDate) {
          const dueDateStr = formatCompactDate(task.dueDate);
          dateInfo += ` [DUE:${dueDateStr}]`;
        }
        if (task.deferDate) {
          const deferDateStr = formatCompactDate(task.deferDate);
          dateInfo += ` [defer:${deferDateStr}]`;
        }
    
        // Format duration
        let durationStr = '';
        if (task.estimatedMinutes) {
          // Convert to hours if >= 60 minutes
          if (task.estimatedMinutes >= 60) {
            const hours = Math.floor(task.estimatedMinutes / 60);
            durationStr = ` (${hours}h)`;
          } else {
            durationStr = ` (${task.estimatedMinutes}m)`;
          }
        }
    
        // Format tags
        let tagsStr = '';
        if (task.tagNames && task.tagNames.length > 0) {
          // Use minimum unique prefixes for tag names
          const abbreviatedTags = task.tagNames.map((tag: string) => {
            return tagPrefixMap.get(tag) || tag;
          });
    
          tagsStr = ` <${abbreviatedTags.join(',')}>`;
        }
    
        // Format status
        let statusStr = '';
        switch (task.taskStatus) {
          case 'Next':
            statusStr = ' #next';
            break;
          case 'Available':
            statusStr = ' #avail';
            break;
          case 'Blocked':
            statusStr = ' #block';
            break;
          case 'DueSoon':
            statusStr = ' #due';
            break;
          case 'Overdue':
            statusStr = ' #over';
            break;
          case 'Completed':
            statusStr = ' #compl';
            break;
          case 'Dropped':
            statusStr = ' #drop';
            break;
        }
    
        let taskOutput = `${indent}• ${flagSymbol}${task.name}${dateInfo}${durationStr}${tagsStr}${statusStr}\n`;
    
        // Process subtasks
        if (task.childIds && task.childIds.length > 0) {
          const childTasks = database.tasks.filter((t: any) => task.childIds.includes(t.id));
    
          for (const childTask of childTasks) {
            taskOutput += processTask(childTask, level + 1);
          }
        }
    
        return taskOutput;
      }
    
      // Process all root folders
      for (const folder of rootFolders) {
        output += processFolder(folder, 0);
      }
    
      // Process projects not in any folder (if any)
      const rootProjects = Object.values(database.projects).filter((project: any) => !project.folderID);
    
      for (const project of rootProjects) {
        output += processProject(project, 0);
      }
    
      // Process tasks in the Inbox (not in any project)
      const inboxTasks = database.tasks.filter(function (task: any) {
        return !task.projectId;
      });
    
      if (inboxTasks.length > 0) {
        output += `\nP: Inbox\n`;
        for (const task of inboxTasks) {
          output += processTask(task, 0);
        }
      }
    
      return output;
    }
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