Skip to main content
Glama

InDesign MCP Server

by chris-enea
server.py13.1 kB
#!/usr/bin/env /Users/honeycomb/indesign_mcp/venv/bin/python """ InDesign MCP Server Provides tools for text manipulation in Adobe InDesign via ExtendScript """ import asyncio import subprocess import json import os from typing import Any, Dict from mcp.server import Server from mcp.server.stdio import stdio_server from mcp import types app = Server("indesign-mcp") @app.list_tools() async def list_tools() -> list[types.Tool]: """List available InDesign text manipulation tools""" return [ types.Tool( name="add_text", description="Add text to an InDesign document", inputSchema={ "type": "object", "properties": { "text": { "type": "string", "description": "The text to add to the document" }, "position": { "type": "string", "description": "Position to add text (start, end, or after_selection)", "enum": ["start", "end", "after_selection"], "default": "end" } }, "required": ["text"] } ), types.Tool( name="update_text", description="Update existing text in an InDesign document", inputSchema={ "type": "object", "properties": { "find_text": { "type": "string", "description": "Text to find and replace" }, "replace_text": { "type": "string", "description": "Text to replace with" }, "all_occurrences": { "type": "boolean", "description": "Replace all occurrences or just the first", "default": False } }, "required": ["find_text", "replace_text"] } ), types.Tool( name="remove_text", description="Remove text from an InDesign document", inputSchema={ "type": "object", "properties": { "text": { "type": "string", "description": "Text to remove from the document" }, "all_occurrences": { "type": "boolean", "description": "Remove all occurrences or just the first", "default": False } }, "required": ["text"] } ), types.Tool( name="get_document_text", description="Get all text content from the active InDesign document", inputSchema={ "type": "object", "properties": {} } ), types.Tool( name="indesign_status", description="Check InDesign application status and document information", inputSchema={ "type": "object", "properties": {} } ) ] async def execute_extendscript(script: str) -> Dict[str, Any]: """Execute ExtendScript in InDesign and return the result""" try: # Create a temporary script file script_path = "/tmp/indesign_script.jsx" with open(script_path, "w") as f: f.write(script) # Try different InDesign application names indesign_apps = [ "Adobe InDesign 2025", "Adobe InDesign 2024", "Adobe InDesign 2023", "Adobe InDesign CC 2024", "Adobe InDesign CC 2023", "Adobe InDesign" ] result = None for app_name in indesign_apps: try: cmd = [ "osascript", "-e", f'tell application "{app_name}" to do script alias POSIX file "{script_path}" language javascript' ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) if result.returncode == 0: break except subprocess.TimeoutExpired: continue except Exception: continue # Clean up the temporary file os.unlink(script_path) if result and result.returncode == 0: return {"success": True, "result": result.stdout.strip()} else: error_msg = result.stderr.strip() if result else "Could not find InDesign application" return {"success": False, "error": error_msg} except subprocess.TimeoutExpired: return {"success": False, "error": "Script execution timed out"} except Exception as e: return {"success": False, "error": str(e)} @app.call_tool() async def call_tool(name: str, arguments: Dict[str, Any]) -> list[types.TextContent]: """Handle tool calls for InDesign text manipulation""" if name == "add_text": text = arguments["text"] position = arguments.get("position", "end") script = f''' try {{ // Check if InDesign is running and has documents if (app.documents.length === 0) {{ throw new Error("No documents are open in InDesign. Please open a document first."); }} var doc = app.activeDocument; if (!doc) {{ throw new Error("No active document found. Please make sure a document is active."); }} // Check if document has text stories if (doc.stories.length === 0) {{ throw new Error("Document has no text stories. Please add a text frame first."); }} var story = doc.stories[0]; var insertionPoint; if ("{position}" === "start") {{ insertionPoint = story.insertionPoints[0]; }} else if ("{position}" === "end") {{ insertionPoint = story.insertionPoints[-1]; }} else {{ insertionPoint = story.insertionPoints[-1]; }} insertionPoint.contents = "{text}"; "Text added successfully to " + doc.name; }} catch (e) {{ "Error: " + e.message; }} ''' result = await execute_extendscript(script) if result["success"]: return [types.TextContent(type="text", text=f"Successfully added text: '{text}'")] else: return [types.TextContent(type="text", text=f"Error adding text: {result['error']}")] elif name == "update_text": find_text = arguments["find_text"] replace_text = arguments["replace_text"] all_occurrences = arguments.get("all_occurrences", False) script = f''' try {{ if (app.documents.length === 0) {{ throw new Error("No documents are open in InDesign. Please open a document first."); }} var doc = app.activeDocument; if (!doc) {{ throw new Error("No active document found."); }} app.findGrepPreferences = NothingEnum.nothing; app.changeGrepPreferences = NothingEnum.nothing; app.findGrepPreferences.findWhat = "{find_text}"; app.changeGrepPreferences.changeTo = "{replace_text}"; var found = doc.changeGrep({"true" if all_occurrences else "false"}); app.findGrepPreferences = NothingEnum.nothing; app.changeGrepPreferences = NothingEnum.nothing; "Replaced " + found.length + " occurrence(s) in " + doc.name; }} catch (e) {{ "Error: " + e.message; }} ''' result = await execute_extendscript(script) if result["success"]: return [types.TextContent(type="text", text=f"Successfully updated text: {result['result']}")] else: return [types.TextContent(type="text", text=f"Error updating text: {result['error']}")] elif name == "remove_text": text = arguments["text"] all_occurrences = arguments.get("all_occurrences", False) script = f''' try {{ if (app.documents.length === 0) {{ throw new Error("No documents are open in InDesign. Please open a document first."); }} var doc = app.activeDocument; if (!doc) {{ throw new Error("No active document found."); }} app.findGrepPreferences = NothingEnum.nothing; app.changeGrepPreferences = NothingEnum.nothing; app.findGrepPreferences.findWhat = "{text}"; app.changeGrepPreferences.changeTo = ""; var found = doc.changeGrep({"true" if all_occurrences else "false"}); app.findGrepPreferences = NothingEnum.nothing; app.changeGrepPreferences = NothingEnum.nothing; "Removed " + found.length + " occurrence(s) from " + doc.name; }} catch (e) {{ "Error: " + e.message; }} ''' result = await execute_extendscript(script) if result["success"]: return [types.TextContent(type="text", text=f"Successfully removed text: {result['result']}")] else: return [types.TextContent(type="text", text=f"Error removing text: {result['error']}")] elif name == "get_document_text": script = ''' try { if (app.documents.length === 0) { throw new Error("No documents are open in InDesign. Please open a document first."); } var doc = app.activeDocument; if (!doc) { throw new Error("No active document found."); } if (doc.stories.length === 0) { return "Document '" + doc.name + "' has no text content."; } var allText = "=== Content from '" + doc.name + "' ===\\n\\n"; for (var i = 0; i < doc.stories.length; i++) { allText += "Story " + (i + 1) + ":\\n"; allText += doc.stories[i].contents + "\\n\\n"; } allText; } catch (e) { "Error: " + e.message; } ''' result = await execute_extendscript(script) if result["success"]: return [types.TextContent(type="text", text=f"Document text content:\\n{result['result']}")] else: return [types.TextContent(type="text", text=f"Error getting document text: {result['error']}")] elif name == "indesign_status": script = ''' try { var status = "=== InDesign Status ===\\n"; status += "Application: " + app.name + " " + app.version + "\\n"; status += "Documents open: " + app.documents.length + "\\n"; if (app.documents.length > 0) { var doc = app.activeDocument; status += "Active document: " + doc.name + "\\n"; status += "Document stories: " + doc.stories.length + "\\n"; status += "Document pages: " + doc.pages.length + "\\n"; if (doc.stories.length > 0) { status += "\\nFirst story preview: " + doc.stories[0].contents.substring(0, 100) + "...\\n"; } } else { status += "\\nNo documents are currently open.\\n"; status += "Please open or create a document in InDesign.\\n"; } status; } catch (e) { "Error checking InDesign status: " + e.message; } ''' result = await execute_extendscript(script) if result["success"]: return [types.TextContent(type="text", text=result['result'])] else: return [types.TextContent(type="text", text=f"Error checking InDesign status: {result['error']}")] else: return [types.TextContent(type="text", text=f"Unknown tool: {name}")] async def main(): async with stdio_server() as streams: await app.run( streams[0], streams[1], app.create_initialization_options() ) if __name__ == "__main__": asyncio.run(main())

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/chris-enea/indesign-mcp'

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