Written by
Matthew Bilyeu
on
on
Poor Man's Computer Use with Execute Arbitrary AppleScript MCP Server
Disclaimer: you should definitely not do this!
I have been playing with Model Context Protocol and just realized you can get proof-of-concept-level computer use with very minimal code if you give the MCP client the ability to execute AppleScript and take screenshots. With just these rough tools you can coax some agentic behavior with a feedback loop.
#!/usr/bin/env python3
from mcp.server.fastmcp import FastMCP, Image
import os
import subprocess
# Initialize the MCP server with a chosen name
mcp = FastMCP("applescript_server", dependencies=["pyautogui", "Pillow"])
@mcp.tool()
def applescript_run(script: str) -> dict:
"""
Executes arbitrary AppleScript via osascript.
Args:
script (str): The AppleScript code to execute.
Returns:
dict: A dictionary containing stdout, stderr, and the return code.
"""
try:
# Run the AppleScript command using osascript
proc = subprocess.run(
['osascript', '-e', script],
capture_output=True,
text=True,
check=False # Allow non-zero exit codes to be returned in the response
)
return {
"stdout": proc.stdout.strip(),
"stderr": proc.stderr.strip(),
"returncode": proc.returncode
}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def take_screenshot() -> Image:
"""
Take a screenshot using AppleScript to execute macOS' screencapture,
forcing JPEG output. If the JPEG data exceeds 1MB, downscale the image
to reduce its size.
"""
import io, tempfile, os
from PIL import Image as PILImage
# Create a temporary file with a .jpg suffix.
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
tmp_filename = tmp_file.name
# Use AppleScript to run the screencapture with JPEG output.
script = f'do shell script "screencapture -t jpg -x \\"{tmp_filename}\\""'
result = applescript_run(script=script)
if result.get("returncode", 0) != 0:
error_msg = result.get("stderr") or "Unknown error during screenshot capture"
return {"error": f"Screenshot failed: {error_msg}"}
try:
# Open the captured image
img = PILImage.open(tmp_filename)
# Function to save image to JPEG buffer with compression.
def save_to_buffer(image):
buf = io.BytesIO()
image.save(buf, format="JPEG", quality=60, optimize=True)
return buf.getvalue()
# Save image and check size.
image_data = save_to_buffer(img)
max_allowed = 1048576 # 1MB
if len(image_data) > max_allowed:
# Downscale the image to reduce size.
new_size = (img.width // 2, img.height // 2)
img = img.resize(new_size, PILImage.LANCZOS)
image_data = save_to_buffer(img)
except Exception as e:
return {"error": f"Error processing screenshot file: {e}"}
finally:
try:
os.remove(tmp_filename)
except Exception:
pass
return Image(data=image_data, format="jpeg")
if __name__ == '__main__':
# Run the server using stdio transport so that it can be invoked by local MCP clients
mcp.run(transport="stdio")