Skip to main content
Glama

After Effects Motion Control Panel

mcp_bridge_fixed.jsx•14.8 kB
// mcp_bridge_fixed.jsx // Fixed version for reliable file-based command handling (function mcpBridge() { // ===== JSON2 Polyfill ===== if(typeof JSON!=='object'){JSON={};} (function(){'use strict';function f(n){return n<10?'0'+n:n;} if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+ f(this.getUTCMonth()+1)+'-'+ f(this.getUTCDate())+'T'+ f(this.getUTCHours())+':'+ f(this.getUTCMinutes())+':'+ f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf();};} var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';} function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);} if(typeof rep==='function'){value=rep.call(holder,key,value);} switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';} gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';} v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;} if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}} v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}} if(typeof JSON.stringify!=='function'){escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;} rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');} return str('',{'':value});};} if(typeof JSON.parse!=='function'){cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}} return reviver.call(holder,key,value);} text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+ ('0000'+a.charCodeAt(0).toString(16)).slice(-4);});} if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;} throw new SyntaxError('JSON.parse');};}}()); // ===== END JSON2 Polyfill ===== // --- State & Config --- var lastModifiedLayer = null; var config = { commandFile: "C:/ae_temp/command.json", resultFile: "C:/ae_temp/result.json", checkInterval: 1000, // Check for commands every second maxRetries: 3 }; // --- UI Panel --- var panel = this instanceof Panel ? this : new Window("palette", "MCP Bridge Fixed", undefined, { resizeable: true }); panel.orientation = "column"; panel.alignChildren = ["fill", "top"]; // Add connection status group var statusGroup = panel.add("group"); statusGroup.orientation = "row"; var statusText = statusGroup.add("statictext", undefined, "Status: File Mode"); // Add control buttons var buttonGroup = panel.add("group"); buttonGroup.orientation = "row"; var checkButton = buttonGroup.add("button", undefined, "Check for Command"); var autoCheckBox = buttonGroup.add("checkbox", undefined, "Auto-Check"); var logBox = panel.add("edittext", [0, 0, 400, 200], "", { multiline: true, readonly: true, scrollable: true }); function log(message) { var timestamp = new Date().toLocaleTimeString(); logBox.text = timestamp + ": " + message + "\n" + logBox.text; } function updateStatus(message) { statusText.text = "Status: " + message; } function writeResult(resultData) { try { var resultFile = new File(config.resultFile); resultFile.open("w"); resultFile.write(JSON.stringify(resultData, null, 2)); resultFile.close(); log("Result file written successfully."); return true; } catch (e) { log("FATAL: Could not write result file: " + e.toString()); return false; } } // --- Action Functions --- function getActiveComp() { var comp = app.project.activeItem; if (!comp || !(comp instanceof CompItem)) { for (var i = 1; i <= app.project.numItems; i++) { if (app.project.item(i) instanceof CompItem) { comp = app.project.item(i); return comp; } } } if (!comp) { log("No comp found. Creating a default one."); comp = app.project.items.addComp("Default Composition", 1920, 1080, 1.0, 10, 30); lastModifiedLayer = null; } return comp; } function createComposition(params) { app.beginUndoGroup("MCP: Create Composition"); var compName = params.name || "New Composition"; var duration = parseFloat(params.duration) || 10; var newComp = app.project.items.addComp(compName, 1920, 1080, 1.0, duration, 30); lastModifiedLayer = null; app.endUndoGroup(); return { status: "success", message: "Composition '" + compName + "' created." }; } function hexToRgb(hex) { try { // Remove # if present hex = hex.replace('#', ''); // Handle named colors var namedColors = { 'red': 'FF0000', 'green': '00FF00', 'blue': '0000FF', 'yellow': 'FFFF00', 'purple': '800080', 'orange': 'FFA500', 'black': '000000', 'white': 'FFFFFF', 'gray': '808080', 'grey': '808080' }; if (namedColors[hex.toLowerCase()]) { hex = namedColors[hex.toLowerCase()]; } // Parse hex values var r = parseInt(hex.substring(0, 2), 16) / 255; var g = parseInt(hex.substring(2, 4), 16) / 255; var b = parseInt(hex.substring(4, 6), 16) / 255; // Validate values if (isNaN(r) || isNaN(g) || isNaN(b)) { throw new Error("Invalid color values"); } return [r, g, b]; } catch (e) { log("Color conversion error: " + e.toString()); // Return default white color if conversion fails return [1, 1, 1]; } } function createTextLayer(params) { app.beginUndoGroup("MCP: Create Text Layer"); try { var comp = getActiveComp(); var textLayer = comp.layers.addText(params.text || "New Text"); var textProp = textLayer.property("Source Text"); var textDocument = textProp.value; // Set font size if provided if (params.fontSize) { textDocument.fontSize = parseFloat(params.fontSize); } // Set font color if provided if (params.color) { var rgbColor = hexToRgb(params.color); textDocument.fillColor = rgbColor; } // Set font family if provided if (params.fontFamily) { textDocument.font = params.fontFamily; } // Apply the changes textProp.setValue(textDocument); // Set position if provided if (params.position && params.position.length === 2) { textLayer.position.setValue([params.position[0], params.position[1], 0]); } else { // Center the text by default textLayer.position.setValue([comp.width / 2, comp.height / 2, 0]); } lastModifiedLayer = textLayer; app.endUndoGroup(); return { status: "success", message: "Text layer created successfully", layerName: textLayer.name }; } catch (e) { app.endUndoGroup(); return { status: "error", message: "Error creating text layer: " + e.toString() }; } } function runCustomCode(params) { app.beginUndoGroup("MCP: Run Custom Code"); try { // Safer approach: validate and print code first var code = params.code; if (!code || typeof code !== "string") { throw new Error("Invalid or empty code provided"); } log("Executing custom code: " + code.substring(0, 100) + "..."); // Create a new Function from the code string and execute it var customFunction = new Function("app", "comp", "project", code); // Get the active composition for context var comp = getActiveComp(); // Execute the function with After Effects context var result = customFunction(app, comp, app.project); app.endUndoGroup(); return { status: "success", message: "Custom code executed successfully", result: result ? result.toString() : "No result returned" }; } catch (e) { app.endUndoGroup(); return { status: "error", message: "Error executing custom code: " + e.toString() }; } } function processCommand(commandData) { try { if (!commandData || !commandData.action) { return { status: "error", message: "Invalid command data or missing action" }; } var action = commandData.action; var params = commandData.params || {}; log("Processing action: " + action); switch (action) { case "create_text_layer": return createTextLayer(params); case "create_composition": return createComposition(params); case "run_custom_code": return runCustomCode(params); default: return { status: "error", message: "Unknown action: " + action }; } } catch (e) { return { status: "error", message: "Error processing command: " + e.toString() }; } } function checkForCommand() { log("Manual check triggered."); var commandFile = new File(config.commandFile); if (commandFile.exists) { log("Command file found!"); try { $.sleep(200); // Wait 200ms to ensure file is fully written commandFile.open("r"); var content = commandFile.read(); commandFile.close(); // Remove the command file commandFile.remove(); try { var commandData = JSON.parse(content); log("Action: " + commandData.action); var result = processCommand(commandData); writeResult(result); } catch (jsonError) { log("Error parsing command JSON: " + jsonError.toString()); writeResult({ status: "error", message: "Error parsing command JSON: " + jsonError.toString() }); } } catch (e) { log("Error during manual check: " + e.toString()); writeResult({ status: "error", message: "Error during manual check: " + e.toString() }); } } else { log("No command file found."); } } // Set up auto-check timer var timer = null; function startAutoCheck() { if (timer) { timer.stop(); } timer = app.scheduleTask('checkForCommand()', config.checkInterval, true); log("Auto-check started. Checking every " + (config.checkInterval / 1000) + " seconds."); } function stopAutoCheck() { if (timer) { timer.stop(); timer = null; log("Auto-check stopped."); } } // Set up UI event handlers checkButton.onClick = checkForCommand; autoCheckBox.onClick = function() { if (autoCheckBox.value) { startAutoCheck(); } else { stopAutoCheck(); } }; // Initialize try { // Create temp directory if it doesn't exist var tempFolder = new Folder("C:/ae_temp"); if (!tempFolder.exists) { tempFolder.create(); } updateStatus("File Mode Active"); log("Running in file mode. Click 'Check for Command' to process commands."); } catch (e) { log("Initialization error: " + e.toString()); updateStatus("Error"); } panel.onResizing = panel.onResize = function() { this.layout.resize(); }; if (panel instanceof Window) { panel.center(); panel.show(); } })();

Latest Blog Posts

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/PankajBagariya/After-Efffect-MCP'

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