Skip to main content
Glama

macOS Automator MCP Server

by steipete
placeholderSubstitutor.ts7.54 kB
export interface SubstitutionResult { substitutedScript: string; logs: string[]; } // Helper functions for KB script argument substitution export function escapeForAppleScriptStringLiteral(value: string): string { return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; } // Helper function to convert camelCase to snake_case function camelToSnake(str: string): string { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } export function valueToAppleScriptLiteral(value: unknown): string { if (typeof value === 'string') { return escapeForAppleScriptStringLiteral(value); } if (typeof value === 'number' || typeof value === 'boolean') { return String(value); } if (Array.isArray(value)) { return `{${value.map(v => valueToAppleScriptLiteral(v)).join(", ")}}`; } if (typeof value === 'object' && value !== null) { const recordParts = Object.entries(value).map(([k, v]) => `${k}:${valueToAppleScriptLiteral(v)}`); return `{${recordParts.join(", ")}}`; } // Consider throwing an error or having a more specific way to log warnings // For now, mirroring server.ts behavior and relying on its logger // logger.warn('Unsupported type for AppleScript literal conversion, using "missing value"', { value }); console.warn('[placeholderSubstitutor] Unsupported type for AppleScript literal conversion, using "missing value"', { value }); return "missing value"; // AppleScript's equivalent of null/undefined (bare keyword) } interface SubstitutePlaceholdersArgs { scriptContent: string; inputData?: Record<string, unknown>; args?: string[]; includeSubstitutionLogs: boolean; // Add a logger instance or a logging callback if fine-grained logging from here is needed // For now, logs are collected and returned. } export function substitutePlaceholders( { scriptContent, inputData, args, includeSubstitutionLogs }: SubstitutePlaceholdersArgs ): SubstitutionResult { let currentScriptContent = scriptContent; const substitutionLogs: string[] = []; const logSub = (message: string, data: unknown) => { const logEntry = `[SUBST] ${message} ${JSON.stringify(data)}`; if (includeSubstitutionLogs) { substitutionLogs.push(logEntry); } }; // JS-style ${inputData.key} const jsInputDataRegex = /\\$\\{inputData\\.(\\w+)\\}/g; logSub('Before jsInputDataRegex', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(jsInputDataRegex, (match, keyName) => { const snakeKeyName = camelToSnake(keyName); // Convert camelCase from script to snake_case for lookup const replacementValue = inputData && snakeKeyName in inputData ? valueToAppleScriptLiteral(inputData[snakeKeyName]) : "missing value"; // Bare keyword logSub('jsInputDataRegex replacing', { match, keyName, snakeKeyName, replacementValue }); return replacementValue; }); logSub('After jsInputDataRegex', { scriptContentLength: currentScriptContent.length }); // JS-style ${arguments[N]} const jsArgumentsRegex = /\\$\\{arguments\\[(\\d+)\\]\\}/g; logSub('Before jsArgumentsRegex', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(jsArgumentsRegex, (match, indexStr) => { const index = Number.parseInt(indexStr, 10); const replacementValue = args && index >= 0 && index < args.length ? valueToAppleScriptLiteral(args[index]) : "missing value"; // Bare keyword logSub('jsArgumentsRegex replacing', { match, indexStr, index, replacementValue }); return replacementValue; }); logSub('After jsArgumentsRegex', { scriptContentLength: currentScriptContent.length }); // Quoted "--MCP_INPUT:keyName" (handles single or double quotes around the placeholder) const quotedMcpInputRegex = /(["'])--MCP_INPUT:(\w+)\1/g; logSub('Before quotedMcpInputRegex (match surrounding quotes)', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(quotedMcpInputRegex, (match, _openingQuote, keyName) => { const snakeKeyName = camelToSnake(keyName); // Convert camelCase from script to snake_case for lookup const replacementValue = inputData && snakeKeyName in inputData ? valueToAppleScriptLiteral(inputData[snakeKeyName]) : "missing value"; logSub('quotedMcpInputRegex (match surrounding quotes) replacing', { match, keyName, snakeKeyName, replacementValue }); return replacementValue; }); logSub('After quotedMcpInputRegex (match surrounding quotes)', { scriptContentLength: currentScriptContent.length }); // Quoted "--MCP_ARG_N" (handles single or double quotes) const quotedMcpArgRegex = /(["'])--MCP_ARG_(\d+)\1/g; logSub('Before quotedMcpArgRegex (match surrounding quotes)', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(quotedMcpArgRegex, (match, _openingQuote, argNumStr) => { const argIndex = Number.parseInt(argNumStr, 10) - 1; const replacementValue = args && argIndex >= 0 && argIndex < args.length ? valueToAppleScriptLiteral(args[argIndex]) : "missing value"; logSub('quotedMcpArgRegex (match surrounding quotes) replacing', { match, argNumStr, argIndex, replacementValue }); return replacementValue; }); logSub('After quotedMcpArgRegex (match surrounding quotes)', { scriptContentLength: currentScriptContent.length }); // Context-aware bare placeholders (not in comments) e.g., in function calls like myFunc(--MCP_INPUT:key) const expressionMcpInputRegex = /([(,=]\s*)--MCP_INPUT:(\w+)\b/g; logSub('Before expressionMcpInputRegex', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(expressionMcpInputRegex, (match, prefix, keyName) => { const snakeKeyName = camelToSnake(keyName); // Convert camelCase from script to snake_case for lookup const replacementValue = inputData && snakeKeyName in inputData ? valueToAppleScriptLiteral(inputData[snakeKeyName]) : "missing value"; logSub('expressionMcpInputRegex replacing', { match, prefix, keyName, snakeKeyName, replacementValue }); return prefix + replacementValue; }); logSub('After expressionMcpInputRegex', { scriptContentLength: currentScriptContent.length }); const expressionMcpArgRegex = /([(,=]\s*)--MCP_ARG_(\d+)\b/g; logSub('Before expressionMcpArgRegex', { scriptContentLength: currentScriptContent.length }); currentScriptContent = currentScriptContent.replace(expressionMcpArgRegex, (match, prefix, argNumStr) => { const argIndex = Number.parseInt(argNumStr, 10) - 1; const replacementValue = args && argIndex >= 0 && argIndex < args.length ? valueToAppleScriptLiteral(args[argIndex]) : "missing value"; logSub('expressionMcpArgRegex replacing', { match, prefix, argNumStr, argIndex, replacementValue }); return prefix + replacementValue; }); logSub('After expressionMcpArgRegex', { scriptContentLength: currentScriptContent.length }); return { substitutedScript: currentScriptContent, logs: substitutionLogs, }; }

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

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