diff --git a/photoshop_mcp_server/ps_adapter/action_manager.py b/photoshop_mcp_server/ps_adapter/action_manager.py index d83e250..ed24c75 100644 --- a/photoshop_mcp_server/ps_adapter/action_manager.py +++ b/photoshop_mcp_server/ps_adapter/action_manager.py @@ -150,8 +150,66 @@ def get_active_document_info(cls) -> dict[str, Any]: except Exception as e: print(f"Error getting document path: {e}") - # Get layers info would require more complex Action Manager code - # This is a simplified implementation + # Get layers info using PhotoshopApp document object + try: + doc = ps_app.get_active_document() + if doc: + # Get art layers + art_layers = [] + for i, layer in enumerate(doc.artLayers): + try: + art_layers.append({ + "index": i, + "name": layer.name, + "visible": layer.visible, + "kind": str(layer.kind), + "opacity": layer.opacity, + "blending_mode": str(layer.blendMode), + }) + except Exception as e: + print(f"Error getting art layer {i}: {e}") + result["layers"] = art_layers + + # Get layer sets (groups) + layer_sets = [] + for i, ls in enumerate(doc.layerSets): + try: + set_layers = [] + for j, layer in enumerate(ls.artLayers): + try: + set_layers.append({ + "index": j, + "name": layer.name, + "visible": layer.visible, + "kind": str(layer.kind), + }) + except Exception: + pass + layer_sets.append({ + "index": i, + "name": ls.name, + "visible": ls.visible, + "layers": set_layers, + }) + except Exception as e: + print(f"Error getting layer set {i}: {e}") + result["layer_sets"] = layer_sets + + # Get channels + channels = [] + for i, ch in enumerate(doc.channels): + try: + channels.append({ + "index": i, + "name": ch.name, + "kind": str(ch.kind), + "visible": ch.visible, + }) + except Exception as e: + print(f"Error getting channel {i}: {e}") + result["channels"] = channels + except Exception as e: + print(f"Error getting layers/layer_sets/channels: {e}") return result diff --git a/photoshop_mcp_server/ps_adapter/application.py b/photoshop_mcp_server/ps_adapter/application.py index cd3a32a..4b8024d 100644 --- a/photoshop_mcp_server/ps_adapter/application.py +++ b/photoshop_mcp_server/ps_adapter/application.py @@ -225,111 +225,89 @@ def execute_javascript(self, script): Returns: str: The result of the JavaScript execution. + Note: + Photoshop uses ExtendScript (ECMAScript 3) which lacks a native + JSON object. A JSON.stringify polyfill is automatically injected. + + comtypes requires all 3 arguments for doJavaScript to dispatch + correctly. Passing only the script string triggers COM error + -2147352567 on most Photoshop versions. + """ - # Ensure script returns a valid JSON string + # Ensure script ends with semicolon if not script.strip().endswith(";"): script = script.rstrip() + ";" - # Make sure script returns a value - if "return " not in script and "JSON.stringify" not in script: - script = script + "\n'success';" # Add a default return value + # Inject JSON polyfill for ExtendScript (ES3 has no native JSON object) + json_polyfill = ( + "if(typeof JSON==='undefined'){JSON={stringify:function(v){" + "if(v===null)return'null';" + "if(typeof v==='number'||typeof v==='boolean')return String(v);" + "if(typeof v==='string')return'\"'+v.replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"';" + "if(v.constructor===Array){var a=[];for(var i=0;i dict: + """Execute JavaScript (JSX) code in Photoshop. + + This is a universal tool that can run any Photoshop JavaScript code, + giving access to the full Photoshop API including operations not covered + by other dedicated tools. + + A JSON.stringify polyfill is automatically injected since Photoshop's + ExtendScript engine is based on ECMAScript 3 which lacks native JSON. + + Args: + script: JavaScript/JSX code to execute in Photoshop. + The script should return a value (string, number, or JSON string). + If no return statement is present, the last expression is returned. + + Returns: + dict: Result containing 'success' flag and 'result' with the script output, + or 'error' if execution failed. + + Examples: + Get layer count: + script: "app.activeDocument.artLayers.length;" + + Get all layer names as JSON: + script: ''' + var doc = app.activeDocument; + var names = []; + for (var i = 0; i < doc.artLayers.length; i++) { + names.push(doc.artLayers[i].name); + } + JSON.stringify(names); + ''' + + Create a rectangle shape: + script: ''' + var doc = app.activeDocument; + var layer = doc.artLayers.add(); + layer.name = "Rectangle"; + var selection = doc.selection; + selection.select([[100,100],[500,100],[500,400],[100,400]]); + selection.fill(app.foregroundColor); + selection.deselect(); + "done"; + ''' + + """ + ps_app = PhotoshopApp() + try: + result = ps_app.execute_javascript(script) + return { + "success": True, + "result": result, + } + except Exception as e: + return { + "success": False, + "error": str(e), + } + + tool_name = register_tool(mcp, execute_jsx, "execute_jsx") + return [tool_name]