Skip to main content
Glama
SuperCrazyKaizen

macOS Automator MCP Server

get_scripting_tips

Find pre-built AppleScript and JXA solutions to automate macOS applications and system tasks using natural language search or category browsing.

Instructions

Discover how to automate any app on your Mac with this comprehensive knowledge base of AppleScript/JXA tips and runnable scripts. This tool is essential for discovery and should be the FIRST CHOICE when aiming to automate macOS tasks, especially those involving common applications or system functions, before attempting to write scripts from scratch. It helps identify pre-built, tested solutions, effectively teaching you how to control virtually any aspect of your macOS experience.

Primary Use Cases & Parameters:

  • Discovering Solutions (Use search_term):

    • Parameter: search_term (string, optional).

    • Functionality: Performs a fuzzy search across all tip titles, descriptions, keywords, script content, and IDs. Ideal for natural language queries like "how to..." (e.g., search_term: "how do I get the current Safari URL and title?"). This is the most common way to find relevant tips.

    • Output: Returns a list of matching tips in Markdown format.

  • Limiting Search Results (Use limit):

    • Parameter: limit (integer, optional, default: 10).

    • Functionality: Specifies the maximum number of script tips to return when using search_term or browsing a specific category (without list_categories: true). Does not apply if list_categories is true.

  • Browsing by Category (Use category):

    • Parameter: category (string, optional).

    • Functionality: Shows tips from a specific category. Combine with limit to control result count.

    • Example: category: "01_intro" or category: "07_browsers/chrome".

  • Listing All Categories (Use list_categories: true):

    • Parameter: list_categories (boolean, optional).

    • Functionality: Returns a structured list of all available categories with their descriptions. This helps you understand what automation areas are covered.

    • Output: Category tree in Markdown format.

  • Refreshing Database (Use refresh_database: true):

    • Parameter: refresh_database (boolean, optional).

    • Functionality: Forces a reload of the knowledge base if new scripts have been added. Typically not needed as the database refreshes automatically.

Best Practices:

  1. Always start with search: Use natural language queries to find solutions (e.g., "send email from Mail app").

  2. Browse categories when exploring: Use list_categories: true to see available automation areas.

  3. Use specific IDs for execution: Once you find a script, use its ID with execute_script tool for precise execution.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
categoryNo
search_termNo
list_categoriesNo
refresh_databaseNo
limitNo

Implementation Reference

  • Core handler function implementing the get_scripting_tips tool logic: handles input parameters, searches knowledge base, formats and limits results, generates Markdown output.
    export async function getScriptingTipsService(
      input: GetScriptingTipsInput,
      serverInfo?: { startTime: string; mode: string; version?: string }
    ): Promise<string> {
      if (input.refresh_database) {
        await forceReloadKnowledgeBase();
      }
      const kb: KnowledgeBaseIndex = await getKnowledgeBase();
    
      let serverDetailsString = "";
      if (serverInfo) {
        const versionInfo = serverInfo.version ? ` Version: ${serverInfo.version}` : "";
        serverDetailsString = `\n\n---\nServer Started: ${serverInfo.startTime}\nExecution Mode: ${serverInfo.mode}${versionInfo}`;
      }
    
      // Handle listCategories separately as it overrides other filters and limit
      if (input.list_categories || (!input.category && !input.search_term && !input.limit)) {
        if (input.list_categories || (!input.category && !input.search_term)) {
            const listCategoriesMessage = handleListCategories(kb, serverInfo?.version);
            return listCategoriesMessage + serverDetailsString;
        }
        if(input.limit && !input.category && !input.search_term){
            const listCategoriesMessage = handleListCategories(kb, serverInfo?.version);
            return `${listCategoriesMessage}\n\nNote: \`limit\` parameter is applied to search results or category browsing, not general listing.${serverDetailsString}`;
        }
      }
    
      const searchResult = performSearch(kb, input.category, input.search_term);
      let noticeAboutLimit = "";
      const actualLimit = input.limit || DEFAULT_TIP_LIMIT; 
    
      if (!input.list_categories && (input.search_term || input.category) && searchResult.tips.length > 0) {
        if (searchResult.tips.length > actualLimit) {
          noticeAboutLimit = `Showing the first ${actualLimit} of ${searchResult.tips.length} matching tips. Use the \`limit\` parameter to adjust this. (Default is 10).\n\n`;
          searchResult.tips = searchResult.tips.slice(0, actualLimit);
        }
      }
    
      if (searchResult.tips.length === 0 && !input.list_categories) { 
        const noResultsMessage = generateNoResultsMessage(input.category, input.search_term);
        return noResultsMessage + serverDetailsString;
      }
    
      const categorizedTips = groupTipsByCategory(searchResult.tips, input.category);
      const formattingResult = formatResultsToMarkdown(categorizedTips, input.category as KnowledgeCategory | undefined);
      const formattedTips = formattingResult.markdownOutput;
      const lineLimitNotice = formattingResult.lineLimitNotice;
    
      let outputMessage: string;
      if (formattedTips.trim() === "") { 
          if (input.list_categories || (!input.category && !input.search_term)) { // Avoid double no-results message if categories were shown
            outputMessage = ""; // Categories were already listed, or will be if no other criteria met
          } else {
            logger.warn('Formatted tips were empty despite having search results (after potential limit).',{input, searchResultTipsCount: searchResult.tips.length});
            outputMessage = generateNoResultsMessage(input.category, input.search_term) + serverDetailsString;
          }
      } else {
          outputMessage = searchResult.notice + noticeAboutLimit + lineLimitNotice + formattedTips;
      }
      
      // If we reached here and outputMessage is empty (e.g. only limit was specified), default to listCategories
      if (outputMessage.trim() === "" && !input.list_categories && !(input.search_term || input.category) ) {
        const listCategoriesMessage = handleListCategories(kb, serverInfo?.version);
        return `${listCategoriesMessage}\n\nNote: \`limit\` parameter applies to search results or category browsing.${serverDetailsString}`;
      }
    
      if (input.refresh_database) {
        outputMessage = `Knowledge base reloaded successfully.${serverDetailsString}\n\n${outputMessage}`;
      } else if (!outputMessage.includes(serverDetailsString) && outputMessage.trim() !== "" && !input.list_categories) {
        // If not refresh, details not already in message, message not empty, and not listCategories (which handles its own details)
        // This is to catch normal search results that didn't go through refresh/listCategories/noResults paths for serverDetailsString
        outputMessage += serverDetailsString;
      }
    
      return outputMessage;
    }
  • Zod schema defining input validation for get_scripting_tips tool, including parameters like category, search_term, list_categories, etc.
    export const GetScriptingTipsInputSchema = z.object({
      category: DynamicScriptingKnowledgeCategoryEnum.optional()
        .describe("Specific category of tips. If omitted with no `search_term`, lists all categories."),
      search_term: z.string().optional()
        .describe("Keyword to search within tip titles, content, keywords, or IDs."),
      list_categories: z.boolean().optional().default(false)
        .describe("If true, returns only the list of available categories and their descriptions. Overrides other parameters."),
      refresh_database: z.boolean().optional().describe("If true, forces a reload of the knowledge base before processing the request."),
      limit: z.number().int().positive().optional().default(10)
        .describe("Maximum number of results to return. Default is 10."),
    });
    
    export type GetScriptingTipsInput = z.infer<typeof GetScriptingTipsInputSchema>;
  • src/server.ts:343-397 (registration)
    MCP server tool registration for 'get_scripting_tips', including description, input shape, and handler that parses input and delegates to getScriptingTipsService.
      // ADD THE NEW TOOL get_scripting_tips HERE
      server.tool(
        'get_scripting_tips',
        `Discover how to automate any app on your Mac with this comprehensive knowledge base of AppleScript/JXA tips and runnable scripts. This tool is essential for discovery and should be the FIRST CHOICE when aiming to automate macOS tasks, especially those involving common applications or system functions, before attempting to write scripts from scratch. It helps identify pre-built, tested solutions, effectively teaching you how to control virtually any aspect of your macOS experience.
    
    **Primary Use Cases & Parameters:**
    
    *   **Discovering Solutions (Use \`search_term\`):**
        *   Parameter: \`search_term\` (string, optional).
        *   Functionality: Performs a fuzzy search across all tip titles, descriptions, keywords, script content, and IDs. Ideal for natural language queries like "how to..." (e.g., \`search_term: "how do I get the current Safari URL and title?"\`). This is the most common way to find relevant tips.
        *   Output: Returns a list of matching tips in Markdown format.
    
    *   **Limiting Search Results (Use \`limit\`):**
        *   Parameter: \`limit\` (integer, optional, default: 10).
        *   Functionality: Specifies the maximum number of script tips to return when using \`search_term\` or browsing a specific \`category\` (without \`list_categories: true\`). Does not apply if \`list_categories\` is true.
    
    *   **Browsing by Category (Use \`category\`):**
        *   Parameter: \`category\` (string, optional).
        *   Functionality: Shows tips from a specific category. Combine with \`limit\` to control result count.
        *   Example: \`category: "01_intro"\` or \`category: "07_browsers/chrome"\`.
    
    *   **Listing All Categories (Use \`list_categories: true\`):**
        *   Parameter: \`list_categories\` (boolean, optional).
        *   Functionality: Returns a structured list of all available categories with their descriptions. This helps you understand what automation areas are covered.
        *   Output: Category tree in Markdown format.
    
    *   **Refreshing Database (Use \`refresh_database: true\`):**
        *   Parameter: \`refresh_database\` (boolean, optional).
        *   Functionality: Forces a reload of the knowledge base if new scripts have been added. Typically not needed as the database refreshes automatically.
    
    **Best Practices:**
    1. **Always start with search**: Use natural language queries to find solutions (e.g., "send email from Mail app").
    2. **Browse categories when exploring**: Use \`list_categories: true\` to see available automation areas.
    3. **Use specific IDs for execution**: Once you find a script, use its ID with \`execute_script\` tool for precise execution.`,
        GetScriptingTipsInputShape,
        async (args: unknown) => {
          const input = GetScriptingTipsInputSchema.parse(args);
          
          // Call getScriptingTipsService directly with the input parameters
          let content = await getScriptingTipsService(input);
    
          // Append first-call info if applicable
          if (!IS_E2E_TESTING && !hasEmittedFirstCallInfo) {
            content += '\n\n' + serverInfoMessage;
            hasEmittedFirstCallInfo = true;
          }
    
          return {
            content: [{
              type: 'text',
              text: content
            }]
          };
        }
      );
  • Helper function to format search results into categorized Markdown output, respecting line limits.
    function formatResultsToMarkdown(
        groupedResults: { category: KnowledgeCategory; tips: ScriptingTip[] }[],
        inputCategory?: KnowledgeCategory | string // Allow string for input.category
    ): { markdownOutput: string; lineLimitNotice: string; tipsRenderedCount: number } { // Updated return type
      if (groupedResults.length === 0) {
        return { markdownOutput: "", lineLimitNotice: "", tipsRenderedCount: 0 };
      }
    
      let cumulativeLineCount = 0;
      let lineLimitNotice = "";
      const outputParts: string[] = [];
      let tipsRenderedCount = 0;
      let firstTipRendered = false;
    
      for (const catResult of groupedResults.sort((a, b) => (a.category as string).localeCompare(b.category as string))) {
        if (lineLimitNotice) break; // Stop if limit was already hit in a previous category
    
        const categoryTitle = formatCategoryTitle(catResult.category as string);
        const categoryHeader = inputCategory ? '' : `## Tips: ${categoryTitle}\n`;
        const categoryHeaderLines = categoryHeader.split('\n').length -1; // -1 because split creates one extra for trailing newline
    
        // Check if category header itself can be added (only if not the first tip overall or if it fits)
        if (firstTipRendered && cumulativeLineCount + categoryHeaderLines > MAX_OUTPUT_LINES) {
            lineLimitNotice = `\n--- Output truncated due to exceeding ~${MAX_OUTPUT_LINES} line limit. ---`;
            break;
        }
        if (categoryHeader) {
            outputParts.push(categoryHeader);
            cumulativeLineCount += categoryHeaderLines;
        }
    
        for (let i = 0; i < catResult.tips.length; i++) {
          const tip = catResult.tips[i];
          const tipMarkdown = formatSingleTipToMarkdownBlock(tip);
          const tipLines = tipMarkdown.split('\n').length - 1;
          const separator = (tipsRenderedCount > 0 || (tipsRenderedCount === 0 && categoryHeader)) ? '\n---\n' : ''; // Add separator if not the very first item
          const separatorLines = separator.split('\n').length -1;
    
          if (!firstTipRendered) {
            // Always render the first tip, regardless of its length
            if (separator) outputParts.push(separator);
            outputParts.push(tipMarkdown);
            cumulativeLineCount += separatorLines + tipLines;
            tipsRenderedCount++;
            firstTipRendered = true;
          } else if (cumulativeLineCount + separatorLines + tipLines <= MAX_OUTPUT_LINES) {
            if (separator) outputParts.push(separator);
            outputParts.push(tipMarkdown);
            cumulativeLineCount += separatorLines + tipLines;
            tipsRenderedCount++;
          } else {
            lineLimitNotice = `\n--- Output truncated due to exceeding ~${MAX_OUTPUT_LINES} line limit. Some tips may have been omitted. ---`;
            break; // Stop adding more tips from this category
          }
        }
      }
      return { markdownOutput: outputParts.join(''), lineLimitNotice, tipsRenderedCount };
    }
  • Core search helper using Fuse.js for fuzzy searching tips by title, id, keywords, description, and script content.
    function searchTips(tipsToSearch: ScriptingTip[], searchTerm: string, customThreshold?: number): ScriptingTip[] {
      if (!searchTerm) {
        return [...tipsToSearch].sort((a, b) => a.title.localeCompare(b.title));
      }
    
      const fuseOptions = {
        isCaseSensitive: false,
        includeScore: false,
        shouldSort: true,
        threshold: customThreshold !== undefined ? customThreshold : PRIMARY_SEARCH_THRESHOLD, // Use custom or default
        keys: FUSE_OPTIONS_KEYS
      };
      const fuse = new Fuse(tipsToSearch, fuseOptions);
      return fuse.search(searchTerm).map(result => result.item);
    }
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behaviors: it's a read-only discovery tool (implied by 'discover' and 'knowledge base'), outputs results in Markdown format, and mentions database refresh functionality. However, it lacks details on rate limits, error handling, or authentication needs, which are minor gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with sections like 'Primary Use Cases & Parameters' and 'Best Practices,' making it easy to scan. However, it is lengthy (over 300 words), which may be excessive for a tool description. Some details could be condensed without losing clarity, though all content is relevant and front-loaded with key purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (5 parameters, 0% schema coverage, no output schema, no annotations), the description is highly complete. It covers purpose, usage, parameters, outputs (Markdown format), and integration with sibling tools. No significant gaps remain for an agent to understand and invoke the tool correctly in context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 0%, so the description must fully compensate. It does so comprehensively: each of the 5 parameters is explained with clear semantics, including functionality, examples (e.g., for 'search_term'), and interactions (e.g., 'limit' applies to 'search_term' or 'category' but not 'list_categories'). This adds significant value beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/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: to 'discover how to automate any app on your Mac' using a 'knowledge base of AppleScript/JXA tips and runnable scripts.' It specifies the verb 'discover' and resource 'tips and scripts,' distinguishing it from the sibling tool 'execute_script' which is for execution rather than discovery. This is specific and avoids tautology.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool: it states it 'should be the FIRST CHOICE when aiming to automate macOS tasks... before attempting to write scripts from scratch.' It also mentions alternatives, advising to 'use its ID with `execute_script` tool for precise execution' after discovery. This includes clear when-to-use and when-not-to-use scenarios.

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/SuperCrazyKaizen/macos-automator-mcp'

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