#
tokens: 1968/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | 
```