Skip to main content
Glama

discover_apps

Discover AppleScript capabilities of macOS applications to enable AI agents to automate tasks like file management, email handling, and contact searches through natural language commands.

Instructions

Discover AppleScript capabilities of a macOS application

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
app_nameYesName of the macOS application to discover
methodYesDiscovery method: basic, dictionary, properties, system_events, comprehensive
destinationYesDirectory path to save discovery results

Implementation Reference

  • src/index.ts:29-356 (registration)
    Tool registration including name, description, and input schema definition for discover_apps
    tools: [
      {
        name: 'discover_apps',
        description: 'Discover AppleScript capabilities of a macOS application',
        inputSchema: {
          type: 'object',
          properties: {
            app_name: {
              type: 'string',
              description: 'Name of the macOS application to discover',
            },
            method: {
              type: 'string',
              description: 'Discovery method: basic, dictionary, properties, system_events, comprehensive',
            },
            destination: {
              type: 'string',
              description: 'Directory path to save discovery results',
            },
          },
          required: ['app_name', 'method', 'destination'],
        },
      },
      {
        name: 'finder_get_selection',
        description: 'Get currently selected files/folders in Finder',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'finder_get_current_folder',
        description: 'Get path of currently viewed folder in frontmost Finder window',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'mail_get_accounts',
        description: 'Get list of all Mail account names',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'mail_get_inbox_count',
        description: 'Get unread message count in inbox',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'mail_get_total_inbox_count',
        description: 'Get total message count in inbox',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'contacts_search_people',
        description: 'Search for people by name in Contacts',
        inputSchema: {
          type: 'object',
          properties: {
            search_term: {
              type: 'string',
              description: 'Name or part of name to search for',
            },
          },
          required: ['search_term'],
        },
      },
      {
        name: 'contacts_get_person_info',
        description: 'Get detailed information for a specific person',
        inputSchema: {
          type: 'object',
          properties: {
            person_name: {
              type: 'string',
              description: 'Full name of the person to get info for',
            },
          },
          required: ['person_name'],
        },
      },
      {
        name: 'reminders_get_lists',
        description: 'Get all reminder lists with reminder counts',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'reminders_get_incomplete_reminders',
        description: 'Get incomplete reminders across all lists',
        inputSchema: {
          type: 'object',
          properties: {
            limit: {
              type: 'number',
              description: 'Maximum number of reminders to return (default: 10)',
            },
          },
        },
      },
      {
        name: 'notes_get_folders',
        description: 'Get all note folders with note counts',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'notes_get_recent_notes',
        description: 'Get recently modified notes',
        inputSchema: {
          type: 'object',
          properties: {
            limit: {
              type: 'number',
              description: 'Maximum number of notes to return (default: 10)',
            },
          },
        },
      },
      {
        name: 'notes_search_notes',
        description: 'Search notes by title or content',
        inputSchema: {
          type: 'object',
          properties: {
            query: {
              type: 'string',
              description: 'Search query for note title or content',
            },
          },
          required: ['query'],
        },
      },
      {
        name: 'notes_create_note',
        description: 'Create new note with title and content',
        inputSchema: {
          type: 'object',
          properties: {
            title: {
              type: 'string',
              description: 'Note title',
            },
            content: {
              type: 'string',
              description: 'Note content/body',
            },
            folder: {
              type: 'string',
              description: 'Target folder name (optional, defaults to "Notes")',
            },
          },
          required: ['title', 'content'],
        },
      },
      {
        name: 'textedit_get_documents',
        description: 'Get list of open TextEdit documents',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'textedit_create_document',
        description: 'Create new TextEdit document with optional content',
        inputSchema: {
          type: 'object',
          properties: {
            content: {
              type: 'string',
              description: 'Optional initial content for the document',
            },
          },
        },
      },
      {
        name: 'calendar_get_calendars',
        description: 'Get all calendars with event counts',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'calendar_create_event',
        description: 'Create new calendar event',
        inputSchema: {
          type: 'object',
          properties: {
            title: {
              type: 'string',
              description: 'Event title/summary',
            },
            start_datetime: {
              type: 'string',
              description: 'Start date and time (YYYY-MM-DD HH:MM format)',
            },
            end_datetime: {
              type: 'string',
              description: 'End date and time (YYYY-MM-DD HH:MM format)',
            },
            calendar: {
              type: 'string',
              description: 'Target calendar name (optional, defaults to "Calendar")',
            },
            notes: {
              type: 'string',
              description: 'Event notes/description (optional)',
            },
          },
          required: ['title', 'start_datetime', 'end_datetime'],
        },
      },
      {
        name: 'calendar_get_today_events',
        description: 'Get today\'s events across all calendars',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'calendar_get_upcoming_events',
        description: 'Get upcoming events in date range',
        inputSchema: {
          type: 'object',
          properties: {
            days: {
              type: 'number',
              description: 'Number of days ahead to look (default: 7)',
            },
          },
        },
      },
      {
        name: 'workflow_contact_to_textedit',
        description: 'Get contact information and create formatted TextEdit document',
        inputSchema: {
          type: 'object',
          properties: {
            name: {
              type: 'string',
              description: 'Person name to look up',
            },
            title: {
              type: 'string',
              description: 'Document title (optional)',
            },
          },
          required: ['name'],
        },
      },
      {
        name: 'mail_create_message',
        description: 'Create new email message with recipients, subject, and body',
        inputSchema: {
          type: 'object',
          properties: {
            to: {
              type: 'string',
              description: 'Recipient email address',
            },
            subject: {
              type: 'string',
              description: 'Email subject',
            },
            body: {
              type: 'string',
              description: 'Email body content',
            },
            cc: {
              type: 'string',
              description: 'CC recipient email address (optional)',
            },
          },
          required: ['to', 'subject', 'body'],
        },
      },
      {
        name: 'mail_send_message',
        description: 'Send the most recently created message',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
      {
        name: 'reminders_create_reminder',
        description: 'Create new reminder with title, optional due date and list',
        inputSchema: {
          type: 'object',
          properties: {
            title: {
              type: 'string',
              description: 'Reminder title',
            },
            due_date: {
              type: 'string',
              description: 'Due date in format YYYY-MM-DD (optional)',
            },
            list: {
              type: 'string',
              description: 'Target reminder list name (optional, defaults to "Reminders")',
            },
            notes: {
              type: 'string',
              description: 'Reminder notes/body (optional)',
            },
          },
          required: ['title'],
        },
      },
    ],
  • The switch case handler for 'discover_apps' tool. Validates inputs (app_name, method, destination), checks directory, executes AppleScript-based discovery (currently implements 'basic' method with app-specific tests for Finder, Contacts, Mail), saves results to file in destination, returns structured response with status and preview.
        case 'discover_apps':
          const appName = (args?.app_name as string) || '';
          const method = (args?.method as string) || '';
          const destination = (args?.destination as string) || '';
          
          // Validate required parameters
          if (!appName || !method || !destination) {
            return {
              content: [
                {
                  type: 'text',
                  text: 'Error: app_name, method, and destination are all required',
                },
              ],
            };
          }
          
          // Validate method parameter
          const validMethods = ['basic', 'dictionary', 'properties', 'system_events', 'comprehensive'];
          if (!validMethods.includes(method)) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Error: method must be one of: ${validMethods.join(', ')}`,
                },
              ],
            };
          }
          
          // Check if destination directory exists
          try {
            const fs = await import('fs');
            const path = await import('path');
            
            if (!fs.existsSync(destination)) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: destination directory does not exist: ${destination}`,
                  },
                ],
              };
            }
            
            if (!fs.statSync(destination).isDirectory()) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: destination is not a directory: ${destination}`,
                  },
                ],
              };
            }
          } catch (error: any) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Error checking destination directory: ${error.message}`,
                },
              ],
            };
          }
          
          // Execute discovery based on method
          try {
            let results = '';
            const fs = await import('fs');
            const path = await import('path');
            
            if (method === 'basic') {
              // Basic availability test
              const appleScript_name = `on run argv
                set appName to item 1 of argv
                tell application appName to get name
              end run`;
              const nameCommand = `osascript -e '${appleScript_name}' -- "${appName}"`;
              const { stdout: nameOutput, stderr: nameError } = await execAsync(nameCommand);
              
              results = `Discovery for ${appName} (Basic Method):\n`;
              results += `Date: ${new Date().toISOString()}\n`;
              results += `Command: ${nameCommand}\n`;
              results += `Output: ${nameOutput.trim()}\n`;
              results += `Error: ${nameError.trim() || 'none'}\n\n`;
              
              // App-specific state verification tests
              if (appName.toLowerCase() === 'finder') {
                const selectionCommand = `osascript -e 'tell application "Finder" to get selection as alias list'`;
                try {
                  const { stdout: selOutput, stderr: selError } = await execAsync(selectionCommand);
                  results += `State Test - Selection:\n`;
                  results += `Command: ${selectionCommand}\n`;
                  results += `Output: ${selOutput.trim()}\n`;
                  results += `Error: ${selError.trim() || 'none'}\n`;
                  results += `Verification: Check if Finder selection matches output\n\n`;
                } catch (selErr: any) {
                  results += `State Test - Selection:\n`;
                  results += `Command: ${selectionCommand}\n`;
                  results += `Error: ${selErr.message}\n\n`;
                }
              }
              
              if (appName.toLowerCase() === 'contacts') {
                const countCommand = `osascript -e 'tell application "Contacts" to get count of people'`;
                try {
                  const { stdout: countOutput, stderr: countError } = await execAsync(countCommand);
                  results += `State Test - Count:\n`;
                  results += `Command: ${countCommand}\n`;
                  results += `Output: ${countOutput.trim()}\n`;
                  results += `Error: ${countError.trim() || 'none'}\n`;
                  results += `Verification: Check if Contacts app shows same count\n\n`;
                } catch (countErr: any) {
                  results += `State Test - Count:\n`;
                  results += `Command: ${countCommand}\n`;
                  results += `Error: ${countErr.message}\n\n`;
                }
              }
              
              if (appName.toLowerCase() === 'mail') {
                const accountsCommand = `osascript -e 'tell application "Mail" to get name of every account'`;
                try {
                  const { stdout: accountsOutput, stderr: accountsError } = await execAsync(accountsCommand);
                  results += `State Test - Accounts:\n`;
                  results += `Command: ${accountsCommand}\n`;
                  results += `Output: ${accountsOutput.trim()}\n`;
                  results += `Error: ${accountsError.trim() || 'none'}\n`;
                  results += `Verification: Check if Mail app shows same accounts\n\n`;
                } catch (accountsErr: any) {
                  results += `State Test - Accounts:\n`;
                  results += `Command: ${accountsCommand}\n`;
                  results += `Error: ${accountsErr.message}\n\n`;
                }
              }
            } else {
              // Placeholder for other methods
              results = `Discovery method '${method}' not yet implemented for ${appName}`;
            }
            
            // Write results to file
            const filename = `${appName.toLowerCase()}_${method}.txt`;
            const filepath = path.join(destination, filename);
            
            // Get preview of file content (first line)
            const preview = results.split('\n')[0];
            const toolVersion = new Date().toISOString();
            
            let status = 'SUCCESS';
            if (method !== 'basic' && results.includes('not yet implemented')) {
              status = 'PARTIAL';
            }
            
            // Append tool response metadata to file for testing verification
            const toolResponse = `\n---\nTOOL RESPONSE:\nSTATUS: ${status}\nMETHOD: ${method}\nAPP: ${appName}\nTOOL_VERSION: ${toolVersion}\nFILE: ${filepath}\nPREVIEW: "${preview}"\n${status === 'SUCCESS' ? 'Discovery completed successfully.' : 'Method not yet implemented - placeholder saved.'}`;
            
            const fullContent = results + toolResponse;
            fs.writeFileSync(filepath, fullContent);
            
            return {
              content: [
                {
                  type: 'text',
                  text: `STATUS: ${status}
    METHOD: ${method}
    APP: ${appName}
    TOOL_VERSION: ${toolVersion}
    FILE: ${filepath}
    PREVIEW: "${preview}"
    ${status === 'SUCCESS' ? 'Discovery completed successfully.' : 'Method not yet implemented - placeholder saved.'}`,
                },
              ],
            };
            
          } catch (error: any) {
            const toolVersion = new Date().toISOString();
            return {
              content: [
                {
                  type: 'text',
                  text: `STATUS: ERROR
    METHOD: ${method}
    APP: ${appName}
    TOOL_VERSION: ${toolVersion}
    ERROR: ${error.message}`,
                },
              ],
            };
          }
          
          try {
            // Basic availability test
            const nameCommand = `osascript -e 'tell application "${appName}" to get name'`;
            const { stdout: nameOutput, stderr: nameError } = await execAsync(nameCommand);
            
            let results = `Discovery for ${appName}:\n`;
            results += `Basic Test - Command: ${nameCommand}\n`;
            results += `Output: ${nameOutput.trim()}\n`;
            results += `Error: ${nameError.trim() || 'none'}\n\n`;
            
            // State verification tests based on app
            if (appName.toLowerCase() === 'finder') {
              const selectionCommand = `osascript -e 'tell application "Finder" to get selection as alias list'`;
              try {
                const { stdout: selOutput, stderr: selError } = await execAsync(selectionCommand);
                results += `State Test - Selection Command: ${selectionCommand}\n`;
                results += `Selection Output: ${selOutput.trim()}\n`;
                results += `Selection Error: ${selError.trim() || 'none'}\n`;
                results += `Verification: Manually check if Finder selection matches output\n\n`;
              } catch (selErr: any) {
                results += `State Test - Selection Command: ${selectionCommand}\n`;
                results += `Selection Error: ${selErr.message}\n\n`;
              }
              
              const folderCommand = `osascript -e 'tell application "Finder" to get POSIX path of (target of front window as alias)'`;
              try {
                const { stdout: folderOutput, stderr: folderError } = await execAsync(folderCommand);
                results += `State Test - Folder Command: ${folderCommand}\n`;
                results += `Folder Output: ${folderOutput.trim()}\n`;
                results += `Folder Error: ${folderError.trim() || 'none'}\n`;
                results += `Verification: Manually check if current Finder folder matches output\n`;
              } catch (folderErr: any) {
                results += `State Test - Folder Command: ${folderCommand}\n`;
                results += `Folder Error: ${folderErr.message}\n`;
              }
            }
            
            if (appName.toLowerCase() === 'contacts') {
              const countCommand = `osascript -e 'tell application "Contacts" to get count of people'`;
              try {
                const { stdout: countOutput, stderr: countError } = await execAsync(countCommand);
                results += `State Test - Count Command: ${countCommand}\n`;
                results += `Count Output: ${countOutput.trim()}\n`;
                results += `Count Error: ${countError.trim() || 'none'}\n`;
                results += `Verification: Manually check if Contacts app shows same number of people\n`;
              } catch (countErr: any) {
                results += `State Test - Count Command: ${countCommand}\n`;
                results += `Count Error: ${countErr.message}\n`;
              }
            }
            
            if (appName.toLowerCase() === 'mail') {
              const accountsCommand = `osascript -e 'tell application "Mail" to get name of every account'`;
              try {
                const { stdout: accountsOutput, stderr: accountsError } = await execAsync(accountsCommand);
                results += `State Test - Accounts Command: ${accountsCommand}\n`;
                results += `Accounts Output: ${accountsOutput.trim()}\n`;
                results += `Accounts Error: ${accountsError.trim() || 'none'}\n`;
                results += `Verification: Manually check if Mail app shows same account names\n`;
              } catch (accountsErr: any) {
                results += `State Test - Accounts Command: ${accountsCommand}\n`;
                results += `Accounts Error: ${accountsErr.message}\n`;
              }
            }
            
            return {
              content: [
                {
                  type: 'text',
                  text: results,
                },
              ],
            };
          } catch (error: any) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Discovery for ${appName}:\nBasic Command: osascript -e 'on run argv...' -- "${appName}"\nError: ${error.message}`,
                },
              ],
            };
          }
Behavior2/5

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

No annotations are provided, so the description carries full burden for behavioral disclosure. It mentions 'discover' which implies a read operation, but doesn't specify what happens during discovery, whether it requires specific permissions, what the output looks like, or any side effects. For a tool with 3 required parameters and no annotations, this is insufficient behavioral context.

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 gets straight to the point. It's appropriately sized for the tool's complexity and contains no unnecessary words. Every word earns its place in conveying the core purpose.

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 3 required parameters, no annotations, and no output schema, the description is incomplete. It doesn't explain what the discovery results look like, how they're saved, what the different methods do, or any behavioral characteristics. For a tool that presumably generates output files and has multiple discovery methods, more context is needed.

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?

Schema description coverage is 100%, so the schema already documents all three parameters with clear descriptions. The description doesn't add any additional parameter semantics beyond what's in the schema. It mentions 'AppleScript capabilities' which relates to the purpose but doesn't elaborate on parameter meanings. Baseline 3 is appropriate when schema does the heavy lifting.

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: 'Discover AppleScript capabilities of a macOS application.' It specifies the verb ('discover') and resource ('AppleScript capabilities'), but doesn't differentiate from siblings since this appears to be the only AppleScript-related tool in the server. The purpose is specific but lacks sibling comparison context.

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 prerequisites, timing considerations, or when not to use it. With many sibling tools for different macOS applications, some implicit context exists, but no explicit usage guidelines are provided.

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

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/samicokar/mcp-mac'

If you have feedback or need assistance with the MCP directory API, please join our Discord server