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;
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool 'Gets the current state,' implying a read operation, but doesn't clarify critical aspects like whether this is a safe, non-destructive action, what permissions are required, or the format/scope of the returned data. For a tool with no annotations and no output schema, this leaves significant gaps in understanding its behavior.

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 directly states the tool's purpose without any unnecessary words. It is appropriately sized and front-loaded, making it easy to parse quickly. Every part of the sentence earns its place by conveying essential information.

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?

Given the complexity (a database dump tool with no annotations and no output schema), the description is incomplete. It lacks details on behavioral traits (e.g., safety, permissions), usage context, and output format. While the schema covers parameters well, the overall context for the agent to invoke this tool correctly is insufficient, especially compared to sibling mutation tools.

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?

The description adds no parameter semantics beyond what the input schema provides. The schema has 100% description coverage, with clear explanations for both parameters ('hideCompleted' and 'hideRecurringDuplicates'), so the baseline is 3. The description doesn't compensate with additional context, such as explaining why these filters are useful or their impact on the output.

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 tool's purpose with a specific verb ('Gets') and resource ('current state of your OmniFocus database'), making it immediately understandable. However, it doesn't explicitly differentiate this tool from its siblings (like 'batch_add_items' or 'edit_item'), which would require a 5. The description avoids tautology by not just restating the name 'dump_database'.

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?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention any prerequisites, context for usage, or exclusions. Given the sibling tools include various mutation operations (e.g., 'add_omnifocus_task', 'remove_item'), the agent must infer that this is a read-only tool for data retrieval, but this is not explicitly stated in the description.

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