run_applescript
Execute AppleScript commands to control macOS applications and automate tasks. Supports multi-line scripts, returns plain text output.
Instructions
Execute AppleScript commands on macOS. Use 'tell application "AppName"' to control apps. Multi-line scripts supported. Returns plain text output, truncated to 10,000 chars for large results.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| script | Yes | AppleScript code to execute. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/mcp_applescript/__init__.py:31-78 (handler)The async handler function for the run_applescript tool. It validates the script using validate_script(), executes it via osascript subprocess, and returns stdout with truncation support.
@mcp.tool() async def run_applescript( script: str = Field(description="AppleScript code to execute."), ) -> str: """ Execute AppleScript commands on macOS. Use 'tell application "AppName"' to control apps. Multi-line scripts supported. Returns plain text output, truncated to 10,000 chars for large results. """ timeout = int(os.getenv("TIMEOUT", "30")) max_output = int(os.getenv("MAX_OUTPUT", "10000")) is_safe, error_message, _ = validate_script(script) if not is_safe: raise RuntimeError(error_message) try: result = subprocess.run( ["osascript", "-e", script], capture_output=True, text=True, timeout=timeout, ) if result.returncode == 0: output = result.stdout.strip() if not output: return "Script executed successfully (no output)" # Truncate if output exceeds limit if len(output) > max_output: truncated = output[:max_output] return ( f"{truncated}\n\n" f"[Output truncated: {len(output):,} characters total, showing first {max_output:,}]" ) return output else: raise RuntimeError(f"AppleScript execution failed: {result.stderr.strip()}") except subprocess.TimeoutExpired as e: raise RuntimeError(f"Script execution timed out after {timeout} seconds") from e except Exception as e: if isinstance(e, RuntimeError): raise raise RuntimeError(f"Failed to execute AppleScript: {str(e)}") from e - src/mcp_applescript/__init__.py:31-31 (registration)The tool is registered via the @mcp.tool() decorator on the FastMCP instance named 'mcp' (line 10).
@mcp.tool() - Input schema using Pydantic Field: takes a single 'script' parameter of type str.
script: str = Field(description="AppleScript code to execute."), ) -> str: - src/mcp_applescript/utils.py:104-141 (helper)Security validation helper called by the handler. Extracts applications from the script, checks against an allowlist (ALLOWED_APPS env var), and detects dangerous patterns (BLOCK_DANGEROUS env var).
def validate_script(script: str) -> tuple[bool, str, dict]: """ Validate AppleScript for security concerns and allowed apps. """ metadata = { "applications": [], "dangerous_patterns": [], "blocked_by": None, } # Extract applications apps = extract_applications(script) metadata["applications"] = apps # Check allowlist allowed_apps = get_allowed_apps() is_allowed, allowlist_error = check_allowed_apps(apps, allowed_apps) if not is_allowed: metadata["blocked_by"] = "allowlist" return False, allowlist_error, metadata # Check dangerous patterns (if enabled) if is_dangerous_blocking_enabled(): dangerous = detect_dangerous_patterns(script) metadata["dangerous_patterns"] = dangerous if dangerous: metadata["blocked_by"] = "dangerous_patterns" patterns_list = "\n".join(f" - {pattern}" for pattern in dangerous) error = ( f"AppleScript blocked: Dangerous pattern(s) detected:\n" f"{patterns_list}\n\n" "To override, set: BLOCK_DANGEROUS=false" ) return False, error, metadata return True, "", metadata - src/mcp_applescript/utils.py:34-41 (helper)Helper used by validate_script to extract application names from 'tell application "X"' patterns in the script.
def extract_applications(script: str) -> list[str]: """ Extract application names from AppleScript. """ pattern = r'tell\s+(?:application|app)\s+"([^"]+)"' matches = re.findall(pattern, script, re.IGNORECASE) return list(set(app.title() for app in matches))