Skip to main content
Glama
peakmojo

AppleScript MCP Server

by peakmojo

applescript_execute

Execute AppleScript code to automate tasks on macOS, such as managing Notes, Calendar, Contacts, Messages, Mail, Finder, Safari, and system functions. Streamline workflows by retrieving, modifying, or organizing data and executing shell commands with ease.

Instructions

Run AppleScript code to interact with Mac applications and system features. This tool can access and manipulate data in Notes, Calendar, Contacts, Messages, Mail, Finder, Safari, and other Apple applications. Common use cases include but not limited to: - Retrieve or create notes in Apple Notes - Access or add calendar events and appointments - List contacts or modify contact details - Search for and organize files using Spotlight or Finder - Get system information like battery status, disk space, or network details - Read or organize browser bookmarks or history - Access or send emails, messages, or other communications - Read, write, or manage file contents - Execute shell commands and capture the output

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
code_snippetYesMulti-line appleScript code to execute
timeoutNoCommand execution timeout in seconds (default: 60)

Implementation Reference

  • Python handler for the 'applescript_execute' tool. Writes AppleScript to temp file and executes with osascript using subprocess.run, handling timeout and errors.
    @server.call_tool()
    async def handle_call_tool(
        name: str, arguments: dict[str, Any] | None
    ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
        """Handle execution of AppleScript to interact with Mac applications and data"""
        try:
            if name == "applescript_execute":
                if not arguments or "code_snippet" not in arguments:
                    raise ValueError("Missing code_snippet argument")
    
                # Get timeout parameter or use default
                timeout = arguments.get("timeout", 60)
                
                # Create temp file for the AppleScript
                with tempfile.NamedTemporaryFile(suffix='.scpt', delete=False) as temp:
                    temp_path = temp.name
                    try:
                        # Write the AppleScript to the temp file
                        temp.write(arguments["code_snippet"].encode('utf-8'))
                        temp.flush()
                        
                        # Execute the AppleScript
                        cmd = ["/usr/bin/osascript", temp_path]
                        result = subprocess.run(
                            cmd, 
                            capture_output=True, 
                            text=True, 
                            timeout=timeout
                        )
                        
                        if result.returncode != 0:
                            error_message = f"AppleScript execution failed: {result.stderr}"
                            return [types.TextContent(type="text", text=error_message)]
                        
                        return [types.TextContent(type="text", text=result.stdout)]
                    except subprocess.TimeoutExpired:
                        return [types.TextContent(type="text", text=f"AppleScript execution timed out after {timeout} seconds")]
                    except Exception as e:
                        return [types.TextContent(type="text", text=f"Error executing AppleScript: {str(e)}")]
                    finally:
                        # Clean up the temporary file
                        try:
                            os.unlink(temp_path)
                        except:
                            pass
            else:
                raise ValueError(f"Unknown tool: {name}")
    
        except Exception as e:
            return [types.TextContent(type="text", text=f"Error: {str(e)}")]
  • JavaScript inline handler for 'applescript_execute' tool, calls executeAppleScript function with optional remote execution support.
    async ({ code_snippet, timeout = 60 }) => {
      logger.info(`Executing AppleScript with timeout ${timeout}s`);
      
      if (!code_snippet) {
        return {
          content: [{ type: 'text', text: 'Error: Missing code_snippet argument' }]
        };
      }
      
      try {
        // Inject configuration variables into the AppleScript environment if needed
        if (code_snippet.includes('{{REMOTE_HOST}}')) {
          code_snippet = code_snippet.replace(/\{\{REMOTE_HOST\}\}/g, config.remoteHost);
        }
        
        if (code_snippet.includes('{{REMOTE_PASSWORD}}')) {
          code_snippet = code_snippet.replace(/\{\{REMOTE_PASSWORD\}\}/g, config.remotePassword);
        }
        
        if (code_snippet.includes('{{REMOTE_USER}}')) {
          code_snippet = code_snippet.replace(/\{\{REMOTE_USER\}\}/g, config.remoteUser);
        }
        
        const result = await executeAppleScript(code_snippet, timeout);
        return {
          content: [{ type: 'text', text: result }]
        };
      } catch (error) {
        return {
          content: [{ type: 'text', text: `Error: ${error.message}` }]
        };
      }
    }
  • Python registration of the 'applescript_execute' tool in the list_tools handler, including schema and description.
        @server.list_tools()
        async def handle_list_tools() -> list[types.Tool]:
            """List available tools"""
            return [
                types.Tool(
                    name="applescript_execute",
                    description="""Run AppleScript code to interact with Mac applications and system features. This tool can access and manipulate data in Notes, Calendar, Contacts, Messages, Mail, Finder, Safari, and other Apple applications. Common use cases include but not limited to:
    - Retrieve or create notes in Apple Notes
    - Access or add calendar events and appointments
    - List contacts or modify contact details
    - Search for and organize files using Spotlight or Finder
    - Get system information like battery status, disk space, or network details
    - Read or organize browser bookmarks or history
    - Access or send emails, messages, or other communications
    - Read, write, or manage file contents
    - Execute shell commands and capture the output
    """,
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "code_snippet": {
                                "type": "string",
                                "description": """Multi-line appleScript code to execute. """
                            },
                            "timeout": {
                                "type": "integer",
                                "description": "Command execution timeout in seconds (default: 60)"
                            }
                        },
                        "required": ["code_snippet"]
                    },
                )
            ]
  • server.js:200-240 (registration)
    JavaScript registration of the 'applescript_execute' tool using server.tool, with Zod schema and inline handler.
    server.tool(
      'applescript_execute',
      'Run AppleScript code to interact with Mac applications and system features. This tool can access and manipulate data in Notes, Calendar, Contacts, Messages, Mail, Finder, Safari, and other Apple applications. Common use cases include but not limited to: - Retrieve or create notes in Apple Notes - Access or add calendar events and appointments - List contacts or modify contact details - Search for and organize files using Spotlight or Finder - Get system information like battery status, disk space, or network details - Read or organize browser bookmarks or history - Access or send emails, messages, or other communications - Read, write, or manage file contents - Execute shell commands and capture the output',
      {
        code_snippet: z.string().describe('Multi-line appleScript code to execute'),
        timeout: z.number().optional().describe('Command execution timeout in seconds (default: 60)')
      },
      async ({ code_snippet, timeout = 60 }) => {
        logger.info(`Executing AppleScript with timeout ${timeout}s`);
        
        if (!code_snippet) {
          return {
            content: [{ type: 'text', text: 'Error: Missing code_snippet argument' }]
          };
        }
        
        try {
          // Inject configuration variables into the AppleScript environment if needed
          if (code_snippet.includes('{{REMOTE_HOST}}')) {
            code_snippet = code_snippet.replace(/\{\{REMOTE_HOST\}\}/g, config.remoteHost);
          }
          
          if (code_snippet.includes('{{REMOTE_PASSWORD}}')) {
            code_snippet = code_snippet.replace(/\{\{REMOTE_PASSWORD\}\}/g, config.remotePassword);
          }
          
          if (code_snippet.includes('{{REMOTE_USER}}')) {
            code_snippet = code_snippet.replace(/\{\{REMOTE_USER\}\}/g, config.remoteUser);
          }
          
          const result = await executeAppleScript(code_snippet, timeout);
          return {
            content: [{ type: 'text', text: result }]
          };
        } catch (error) {
          return {
            content: [{ type: 'text', text: `Error: ${error.message}` }]
          };
        }
      }
    );
  • JavaScript helper function that dispatches to local or remote AppleScript execution based on config.
    async function executeAppleScript(code, timeout = 60) {
      // Check if all remote credentials are available for SSH execution
      const useRemote = config.remoteHost && config.remoteHost !== 'localhost' &&
                        config.remoteUser && config.remotePassword;
      
      if (useRemote) {
        return executeRemoteAppleScript(code, timeout);
      } else {
        return executeLocalAppleScript(code, timeout);
      }
    }
    
    async function executeLocalAppleScript(code, timeout = 60) {
      // Create a temporary file for the AppleScript
      const tempPath = path.join(os.tmpdir(), `applescript_${Date.now()}.scpt`);
      
      try {
        // Write the AppleScript to the temporary file
        fs.writeFileSync(tempPath, code);
        
        // Execute the AppleScript
        return new Promise((resolve, reject) => {
          exec(`/usr/bin/osascript "${tempPath}"`, { timeout: timeout * 1000 }, (error, stdout, stderr) => {
            // Clean up the temporary file
            try {
              fs.unlinkSync(tempPath);
            } catch (e) {
              logger.warn(`Failed to delete temporary file: ${e.message}`);
            }
            
            if (error) {
              if (error.killed) {
                resolve(`AppleScript execution timed out after ${timeout} seconds`);
              } else {
                resolve(`AppleScript execution failed: ${stderr}`);
              }
            } else {
              resolve(stdout);
            }
          });
        });
      } catch (e) {
        return `Error executing AppleScript: ${e.message}`;
      }
    }
    
    async function executeRemoteAppleScript(code, timeout = 60) {
      logger.info(`Executing AppleScript on remote host: ${config.remoteHost}`);
      
      // Create a temporary file for the AppleScript
      const localTempPath = path.join(os.tmpdir(), `applescript_${Date.now()}.scpt`);
      const remoteTempPath = `/tmp/applescript_${Date.now()}.scpt`;
      
      try {
        // Write the AppleScript to the temporary file
        fs.writeFileSync(localTempPath, code);
        
        // Initialize SSH client
        const ssh = new NodeSSH();
        
        // Connect to remote host
        try {
          await ssh.connect({
            host: config.remoteHost,
            username: config.remoteUser,
            password: config.remotePassword,
            // Useful when password auth fails and you need to try keyboard-interactive
            tryKeyboard: true,
            onKeyboardInteractive: (name, instructions, lang, prompts, finish) => {
              if (prompts.length > 0 && prompts[0].prompt.toLowerCase().includes('password')) {
                finish([config.remotePassword]);
              }
            }
          });
          
          logger.info('SSH connection established successfully');
          
          // Upload the AppleScript file
          await ssh.putFile(localTempPath, remoteTempPath);
          
          // Execute the AppleScript on the remote machine
          const result = await ssh.execCommand(`/usr/bin/osascript "${remoteTempPath}"`, {
            timeout: timeout * 1000
          });
          
          // Clean up the remote file
          await ssh.execCommand(`rm -f "${remoteTempPath}"`);
          
          // Clean up the local file
          try {
            fs.unlinkSync(localTempPath);
          } catch (e) {
            logger.warn(`Failed to delete local temporary file: ${e.message}`);
          }
          
          // Disconnect from the remote host
          ssh.dispose();
          
          if (result.code !== 0) {
            return `Remote AppleScript execution failed: ${result.stderr}`;
          }
          
          return result.stdout;
        } catch (sshError) {
          // Clean up the local file on error
          try {
            fs.unlinkSync(localTempPath);
          } catch (e) {
            logger.warn(`Failed to delete local temporary file: ${e.message}`);
          }
          
          return `SSH error: ${sshError.message}`;
        }
      } catch (e) {
        return `Error executing remote AppleScript: ${e.message}`;
      }
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions capabilities like manipulating data and executing commands, but fails to disclose critical traits such as security implications, permission requirements, error handling, or potential side effects (e.g., data modification risks). This leaves significant gaps for a tool with broad system access.

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 appropriately sized and front-loaded, starting with the core purpose and followed by a bulleted list of use cases. While the list is extensive, each item earns its place by illustrating the tool's scope, making it efficient for understanding capabilities without unnecessary verbosity.

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

Completeness3/5

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

Given the tool's complexity (broad system access, 2 parameters, no annotations, no output schema), the description is partially complete. It covers purpose and use cases well but lacks behavioral details (e.g., safety, errors) and output information. This is adequate for basic understanding but insufficient for fully informed usage without additional context.

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?

The schema description coverage is 100%, so the schema already documents both parameters (code_snippet and timeout). The description does not add any meaning beyond what the schema provides, such as syntax examples or best practices for AppleScript code. Baseline 3 is appropriate as the schema handles parameter documentation adequately.

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 with a specific verb ('Run AppleScript code') and resource ('Mac applications and system features'), and distinguishes it from potential siblings by listing comprehensive capabilities like interacting with Notes, Calendar, Contacts, Finder, Safari, and executing shell commands. It goes beyond a tautology by detailing what the tool can achieve.

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

Usage Guidelines3/5

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

The description implies usage through examples of common use cases (e.g., retrieve notes, access calendar events, execute shell commands), but it does not explicitly state when to use this tool versus alternatives or provide exclusions. With no sibling tools, the guidance is adequate but lacks explicit context for decision-making.

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

Related 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/peakmojo/applescript-mcp'

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