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

    • 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

    • 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

    • 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

    • 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

    • 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); }

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