# Directory Structure ``` ├── .gitignore ├── pyproject.toml ├── README.md ├── src │ └── illustrator │ ├── __init__.py │ └── server.py └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Illustrator MCP Server 2 | Adobe Illustrator is compatible with JavaScript. In fact, some super big stuff you need to programmatically generate with these scripts. Bots are good at JavaScript. 3 | 4 | This MCP server let's bots send scripts straight to Illustrator and look at the result. 5 | 6 | Since it depends on AppleScript, it's only compatible with MacOS. and I've only tested it with Claude Desktop. 7 | `~/Library/Application\ Support/Claude/claude_desktop_config.json` 8 | 9 | ``` 10 | { 11 | "mcpServers": { 12 | "illustrator": { 13 | "command": "uv", 14 | "args": [ 15 | "--directory", 16 | "/Users/you/code/mcp/illustrator-mcp-server", 17 | "run", 18 | "illustrator" 19 | ] 20 | } 21 | } 22 | } 23 | ``` 24 | ``` -------------------------------------------------------------------------------- /src/illustrator/__init__.py: -------------------------------------------------------------------------------- ```python 1 | from . import server 2 | import asyncio 3 | 4 | 5 | def main(): 6 | """Main entry point for the package.""" 7 | asyncio.run(server.main()) 8 | 9 | 10 | # Optionally expose other important items at package level 11 | __all__ = ["main", "server"] 12 | ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "illustrator" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "httpx>=0.28.1", 10 | "mcp>=1.1.1", 11 | "pillow>=11.0.0", 12 | ] 13 | 14 | [project.scripts] 15 | illustrator = "illustrator:main" 16 | ``` -------------------------------------------------------------------------------- /src/illustrator/server.py: -------------------------------------------------------------------------------- ```python 1 | import subprocess 2 | import tempfile 3 | import os 4 | import asyncio 5 | import mcp.types as types 6 | from mcp.server.models import InitializationOptions 7 | from mcp.server import NotificationOptions, Server 8 | import mcp.server.stdio 9 | import base64 10 | from PIL import Image 11 | import io 12 | 13 | 14 | server = Server("illustrator") 15 | 16 | 17 | @server.list_tools() 18 | async def handle_list_tools() -> list[types.Tool]: 19 | return [ 20 | types.Tool( 21 | name="view", 22 | description="View a screenshot of the Adobe Ullustrator window", 23 | inputSchema={ 24 | "type": "object", 25 | "properties": {}, 26 | }, 27 | ), 28 | types.Tool( 29 | name="run", 30 | description="Run ExtendScript code in Illustrator", 31 | inputSchema={ 32 | "type": "object", 33 | "properties": { 34 | "code": { 35 | "type": "string", 36 | "description": "ExtendScript/JavaScript code to execute in Illustrator. It will run on the current document. you only need to make the document once", 37 | } 38 | }, 39 | "required": ["code"], 40 | }, 41 | ), 42 | ] 43 | 44 | 45 | def captureIllustrator() -> list[types.TextContent | types.ImageContent]: 46 | with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: 47 | screenshot_path = f.name 48 | 49 | try: 50 | activate_script = """ 51 | tell application "Adobe Illustrator" to activate 52 | delay 1 53 | tell application "Claude" to activate 54 | """ 55 | subprocess.run(["osascript", "-e", activate_script]) 56 | 57 | result = subprocess.run( 58 | [ 59 | "screencapture", 60 | "-R", 61 | "0,0,960,1080", 62 | "-C", 63 | "-T", 64 | "2", 65 | "-x", 66 | screenshot_path, 67 | ] 68 | ) 69 | 70 | if result.returncode != 0: 71 | return [types.TextContent(type="text", text="Failed to capture screenshot")] 72 | 73 | with Image.open(screenshot_path) as img: 74 | if img.mode in ("RGBA", "LA"): 75 | img = img.convert("RGB") 76 | buffer = io.BytesIO() 77 | img.save(buffer, format="JPEG", quality=50, optimize=True) 78 | compressed_data = buffer.getvalue() 79 | screenshot_data = base64.b64encode(compressed_data).decode("utf-8") 80 | 81 | return [ 82 | types.ImageContent( 83 | type="image", 84 | mimeType="image/jpeg", 85 | data=screenshot_data, 86 | ) 87 | ] 88 | 89 | finally: 90 | if os.path.exists(screenshot_path): 91 | os.unlink(screenshot_path) 92 | 93 | 94 | def runIllustratorScript(code: str) -> list[types.TextContent]: 95 | script = code.replace('"', '\\"').replace("\n", "\\n") 96 | 97 | applescript = f""" 98 | tell application "Adobe Illustrator" 99 | do javascript "{script}" 100 | end tell 101 | """ 102 | 103 | result = subprocess.run( 104 | ["osascript", "-e", applescript], capture_output=True, text=True 105 | ) 106 | 107 | if result.returncode != 0: 108 | return [ 109 | types.TextContent( 110 | type="text", text=f"Error executing script: {result.stderr}" 111 | ) 112 | ] 113 | 114 | success_message = "Script executed successfully" 115 | if result.stdout: 116 | success_message += f"\nOutput: {result.stdout}" 117 | 118 | return [types.TextContent(type="text", text=success_message)] 119 | 120 | 121 | @server.call_tool() 122 | async def handleCallTool( 123 | name: str, arguments: dict | None 124 | ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: 125 | if name == "view": 126 | return captureIllustrator() 127 | elif name == "run": 128 | if not arguments or "code" not in arguments: 129 | return [types.TextContent(type="text", text="No code provided")] 130 | return runIllustratorScript(arguments["code"]) 131 | else: 132 | raise ValueError(f"Unknown tool: {name}") 133 | 134 | 135 | async def main(): 136 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 137 | await server.run( 138 | read_stream, 139 | write_stream, 140 | InitializationOptions( 141 | server_name="illustrator", 142 | server_version="0.1.0", 143 | capabilities=server.get_capabilities( 144 | notification_options=NotificationOptions(), 145 | experimental_capabilities={}, 146 | ), 147 | ), 148 | ) 149 | 150 | 151 | if __name__ == "__main__": 152 | asyncio.run(main()) 153 | ```