Skip to main content
Glama

mcp-after-effects

by Dakkshin
index.ts32 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { execSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; import { z } from "zod"; import { fileURLToPath } from 'url'; // Create an MCP server const server = new McpServer({ name: "AfterEffectsServer", version: "1.0.0" }); // ES Modules replacement for __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Define paths const SCRIPTS_DIR = path.join(__dirname, "scripts"); const TEMP_DIR = path.join(__dirname, "temp"); // Helper function to run After Effects scripts function runExtendScript(scriptPath: string, args: Record<string, any> = {}): string { try { // Ensure temp directory exists if (!fs.existsSync(TEMP_DIR)) { fs.mkdirSync(TEMP_DIR, { recursive: true }); } // Create a temporary file to hold the script arguments const argsPath = path.join(TEMP_DIR, "args.json"); fs.writeFileSync(argsPath, JSON.stringify(args)); // Find After Effects executable location - modify as needed for your installation // This is a common default location, adjust as necessary const aePath = "C:\\Program Files\\Adobe\\Adobe After Effects 2025\\Support Files\\AfterFX.exe"; // Verify After Effects executable exists if (!fs.existsSync(aePath)) { return `Error: After Effects executable not found at "${aePath}". Please check your installation.`; } // Verify script file exists if (!fs.existsSync(scriptPath)) { return `Error: Script file not found at "${scriptPath}".`; } // Try using the -m flag instead of -r for running scripts (alternative method) // The -m flag tells After Effects to run a script without showing a dialog const command = `"${aePath}" -m "${scriptPath}" "${argsPath}"`; console.error(`Running command with -m flag: ${command}`); try { const output = execSync(command, { encoding: 'utf8', timeout: 30000 }); return output; } catch (execError: any) { console.error("Command execution error:", execError); // If -m flag fails, try creating a JSX file that calls the script via BridgeTalk // This is a different approach that can work if direct execution fails console.error("Trying alternative approach using BridgeTalk..."); const bridgeScriptPath = path.join(TEMP_DIR, "bridge_script.jsx"); const bridgeScriptContent = ` #include "${scriptPath.replace(/\\/g, "/")}" alert("Script execution completed"); `; fs.writeFileSync(bridgeScriptPath, bridgeScriptContent); return `Error executing After Effects command: ${String(execError?.message || execError)}. This might be because After Effects cannot be accessed in headless mode. Please try running the script "${path.basename(scriptPath)}" manually in After Effects.`; } } catch (error) { console.error("Error running ExtendScript:", error); return `Error: ${String(error)}`; } } // Helper function to read results from After Effects temp file function readResultsFromTempFile(): string { try { const tempFilePath = path.join(process.env.TEMP || process.env.TMP || '', 'ae_mcp_result.json'); // Add debugging info console.error(`Checking for results at: ${tempFilePath}`); if (fs.existsSync(tempFilePath)) { // Get file stats to check modification time const stats = fs.statSync(tempFilePath); console.error(`Result file exists, last modified: ${stats.mtime.toISOString()}`); const content = fs.readFileSync(tempFilePath, 'utf8'); console.error(`Result file content length: ${content.length} bytes`); // If the result file is older than 30 seconds, warn the user const thirtySecondsAgo = new Date(Date.now() - 30 * 1000); if (stats.mtime < thirtySecondsAgo) { console.error(`WARNING: Result file is older than 30 seconds. After Effects may not be updating results.`); return JSON.stringify({ warning: "Result file appears to be stale (not recently updated).", message: "This could indicate After Effects is not properly writing results or the MCP Bridge Auto panel isn't running.", lastModified: stats.mtime.toISOString(), originalContent: content }); } return content; } else { console.error(`Result file not found at: ${tempFilePath}`); return JSON.stringify({ error: "No results file found. Please run a script in After Effects first." }); } } catch (error) { console.error("Error reading results file:", error); return JSON.stringify({ error: `Failed to read results: ${String(error)}` }); } } // Helper function to write command to file function writeCommandFile(command: string, args: Record<string, any> = {}): void { try { const commandFile = path.join(process.env.TEMP || process.env.TMP || '', 'ae_command.json'); const commandData = { command, args, timestamp: new Date().toISOString(), status: "pending" // pending, running, completed, error }; fs.writeFileSync(commandFile, JSON.stringify(commandData, null, 2)); console.error(`Command "${command}" written to ${commandFile}`); } catch (error) { console.error("Error writing command file:", error); } } // Helper function to clear the results file to avoid stale cache function clearResultsFile(): void { try { const resultFile = path.join(process.env.TEMP || process.env.TMP || '', 'ae_mcp_result.json'); // Write a placeholder message to indicate the file is being reset const resetData = { status: "waiting", message: "Waiting for new result from After Effects...", timestamp: new Date().toISOString() }; fs.writeFileSync(resultFile, JSON.stringify(resetData, null, 2)); console.error(`Results file cleared at ${resultFile}`); } catch (error) { console.error("Error clearing results file:", error); } } // Add a resource to expose project compositions server.resource( "compositions", "aftereffects://compositions", async (uri) => { const scriptPath = path.join(SCRIPTS_DIR, "listCompositions.jsx"); console.error(`Using script path: ${scriptPath}`); const result = runExtendScript(scriptPath); return { contents: [{ uri: uri.href, mimeType: "application/json", text: result }] }; } ); // Add a tool for running read-only scripts server.tool( "run-script", "Run a read-only script in After Effects", { script: z.string().describe("Name of the predefined script to run"), parameters: z.record(z.any()).optional().describe("Optional parameters for the script") }, async ({ script, parameters = {} }) => { // Validate that script is safe (only allow predefined scripts) const allowedScripts = [ "listCompositions", "getProjectInfo", "getLayerInfo", "createComposition", "createTextLayer", "createShapeLayer", "createSolidLayer", "setLayerProperties", "setLayerKeyframe", "setLayerExpression", "applyEffect", "applyEffectTemplate", "test-animation", "bridgeTestEffects" ]; if (!allowedScripts.includes(script)) { return { content: [ { type: "text", text: `Error: Script "${script}" is not allowed. Allowed scripts are: ${allowedScripts.join(", ")}` } ], isError: true }; } try { // Clear any stale result data clearResultsFile(); // Write command to file for After Effects to pick up writeCommandFile(script, parameters); return { content: [ { type: "text", text: `Command to run "${script}" has been queued.\n` + `Please ensure the "MCP Bridge Auto" panel is open in After Effects.\n` + `Use the "get-results" tool after a few seconds to check for results.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing command: ${String(error)}` } ], isError: true }; } } ); // Add a tool to get the results from the last script execution server.tool( "get-results", "Get results from the last script executed in After Effects", {}, async () => { try { const result = readResultsFromTempFile(); return { content: [ { type: "text", text: result } ] }; } catch (error) { return { content: [ { type: "text", text: `Error getting results: ${String(error)}` } ], isError: true }; } } ); // Add prompts for common After Effects tasks server.prompt( "list-compositions", "List compositions in the current After Effects project", () => { return { messages: [{ role: "user", content: { type: "text", text: "Please list all compositions in the current After Effects project." } }] }; } ); server.prompt( "analyze-composition", { compositionName: z.string().describe("Name of the composition to analyze") }, (args) => { return { messages: [{ role: "user", content: { type: "text", text: `Please analyze the composition named "${args.compositionName}" in the current After Effects project. Provide details about its duration, frame rate, resolution, and layers.` } }] }; } ); // Add a prompt for creating compositions server.prompt( "create-composition", "Create a new composition with specified settings", () => { return { messages: [{ role: "user", content: { type: "text", text: `Please create a new composition with custom settings. You can specify parameters like name, width, height, frame rate, etc.` } }] }; } ); // Add a tool to provide help and instructions server.tool( "get-help", "Get help on using the After Effects MCP integration", {}, async () => { return { content: [ { type: "text", text: `# After Effects MCP Integration Help To use this integration with After Effects, follow these steps: 1. **Install the scripts in After Effects** - Run \`node install-script.js\` with administrator privileges - This copies the necessary scripts to your After Effects installation 2. **Open After Effects** - Launch Adobe After Effects - Open a project that you want to work with 3. **Open the MCP Bridge Auto panel** - In After Effects, go to Window > mcp-bridge-auto.jsx - The panel will automatically check for commands every few seconds 4. **Run scripts through MCP** - Use the \`run-script\` tool to queue a command - The Auto panel will detect and run the command automatically - Results will be saved to a temp file 5. **Get results through MCP** - After a command is executed, use the \`get-results\` tool - This will retrieve the results from After Effects Available scripts: - getProjectInfo: Information about the current project - listCompositions: List all compositions in the project - getLayerInfo: Information about layers in the active composition - createComposition: Create a new composition - createTextLayer: Create a new text layer - createShapeLayer: Create a new shape layer - createSolidLayer: Create a new solid layer - setLayerProperties: Set properties for a layer - setLayerKeyframe: Set a keyframe for a layer property - setLayerExpression: Set an expression for a layer property - applyEffect: Apply an effect to a layer - applyEffectTemplate: Apply a predefined effect template to a layer Effect Templates: - gaussian-blur: Simple Gaussian blur effect - directional-blur: Motion blur in a specific direction - color-balance: Adjust hue, lightness, and saturation - brightness-contrast: Basic brightness and contrast adjustment - curves: Advanced color adjustment using curves - glow: Add a glow effect to elements - drop-shadow: Add a customizable drop shadow - cinematic-look: Combination of effects for a cinematic appearance - text-pop: Effects to make text stand out (glow and shadow) Note: The auto-running panel can be left open in After Effects to continuously listen for commands from external applications.` } ] }; } ); // Add a tool specifically for creating compositions server.tool( "create-composition", "Create a new composition in After Effects with specified parameters", { name: z.string().describe("Name of the composition"), width: z.number().int().positive().describe("Width of the composition in pixels"), height: z.number().int().positive().describe("Height of the composition in pixels"), pixelAspect: z.number().positive().optional().describe("Pixel aspect ratio (default: 1.0)"), duration: z.number().positive().optional().describe("Duration in seconds (default: 10.0)"), frameRate: z.number().positive().optional().describe("Frame rate in frames per second (default: 30.0)"), backgroundColor: z.object({ r: z.number().int().min(0).max(255), g: z.number().int().min(0).max(255), b: z.number().int().min(0).max(255) }).optional().describe("Background color of the composition (RGB values 0-255)") }, async (params) => { try { // Write command to file for After Effects to pick up writeCommandFile("createComposition", params); return { content: [ { type: "text", text: `Command to create composition "${params.name}" has been queued.\n` + `Please ensure the "MCP Bridge Auto" panel is open in After Effects.\n` + `Use the "get-results" tool after a few seconds to check for results.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing composition creation: ${String(error)}` } ], isError: true }; } } ); // --- BEGIN NEW TOOLS --- // Zod schema for common layer identification const LayerIdentifierSchema = { compIndex: z.number().int().positive().describe("1-based index of the target composition in the project panel."), layerIndex: z.number().int().positive().describe("1-based index of the target layer within the composition.") }; // Zod schema for keyframe value (more specific types might be needed depending on property) // Using z.any() for flexibility, but can be refined (e.g., z.array(z.number()) for position/scale) const KeyframeValueSchema = z.any().describe("The value for the keyframe (e.g., [x,y] for Position, [w,h] for Scale, angle for Rotation, percentage for Opacity)"); // Tool for setting a layer keyframe server.tool( "setLayerKeyframe", // Corresponds to the function name in ExtendScript "Set a keyframe for a specific layer property at a given time.", { ...LayerIdentifierSchema, // Reuse common identifiers propertyName: z.string().describe("Name of the property to keyframe (e.g., 'Position', 'Scale', 'Rotation', 'Opacity')."), timeInSeconds: z.number().describe("The time (in seconds) for the keyframe."), value: KeyframeValueSchema }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("setLayerKeyframe", parameters); return { content: [ { type: "text", text: `Command to set keyframe for "${parameters.propertyName}" on layer ${parameters.layerIndex} in comp ${parameters.compIndex} has been queued.\n` + `Use the "get-results" tool after a few seconds to check for confirmation.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing setLayerKeyframe command: ${String(error)}` } ], isError: true }; } } ); // Tool for setting a layer expression server.tool( "setLayerExpression", // Corresponds to the function name in ExtendScript "Set or remove an expression for a specific layer property.", { ...LayerIdentifierSchema, // Reuse common identifiers propertyName: z.string().describe("Name of the property to apply the expression to (e.g., 'Position', 'Scale', 'Rotation', 'Opacity')."), expressionString: z.string().describe("The JavaScript expression string. Provide an empty string (\"\") to remove the expression.") }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("setLayerExpression", parameters); return { content: [ { type: "text", text: `Command to set expression for "${parameters.propertyName}" on layer ${parameters.layerIndex} in comp ${parameters.compIndex} has been queued.\n` + `Use the "get-results" tool after a few seconds to check for confirmation.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing setLayerExpression command: ${String(error)}` } ], isError: true }; } } ); // --- END NEW TOOLS --- // --- BEGIN NEW TESTING TOOL --- // Add a special tool for directly testing the keyframe functionality server.tool( "test-animation", "Test animation functionality in After Effects", { operation: z.enum(["keyframe", "expression"]).describe("The animation operation to test"), compIndex: z.number().int().positive().describe("Composition index (usually 1)"), layerIndex: z.number().int().positive().describe("Layer index (usually 1)") }, async (params) => { try { // Generate a unique timestamp const timestamp = new Date().getTime(); const tempFile = path.join(process.env.TEMP || process.env.TMP || '', `ae_test_${timestamp}.jsx`); // Create a direct test script that doesn't rely on command files let scriptContent = ""; if (params.operation === "keyframe") { scriptContent = ` // Direct keyframe test script try { var comp = app.project.items[${params.compIndex}]; var layer = comp.layers[${params.layerIndex}]; var prop = layer.property("Transform").property("Opacity"); var time = 1; // 1 second var value = 25; // 25% opacity // Set a keyframe prop.setValueAtTime(time, value); // Write direct result var resultFile = new File("${path.join(process.env.TEMP || process.env.TMP || '', 'ae_test_result.txt').replace(/\\/g, '\\\\')}"); resultFile.open("w"); resultFile.write("SUCCESS: Added keyframe at time " + time + " with value " + value); resultFile.close(); // Visual feedback alert("Test successful: Added opacity keyframe at " + time + "s with value " + value + "%"); } catch (e) { var errorFile = new File("${path.join(process.env.TEMP || process.env.TMP || '', 'ae_test_error.txt').replace(/\\/g, '\\\\')}"); errorFile.open("w"); errorFile.write("ERROR: " + e.toString()); errorFile.close(); alert("Test failed: " + e.toString()); } `; } else if (params.operation === "expression") { scriptContent = ` // Direct expression test script try { var comp = app.project.items[${params.compIndex}]; var layer = comp.layers[${params.layerIndex}]; var prop = layer.property("Transform").property("Position"); var expression = "wiggle(3, 30)"; // Set the expression prop.expression = expression; // Write direct result var resultFile = new File("${path.join(process.env.TEMP || process.env.TMP || '', 'ae_test_result.txt').replace(/\\/g, '\\\\')}"); resultFile.open("w"); resultFile.write("SUCCESS: Added expression: " + expression); resultFile.close(); // Visual feedback alert("Test successful: Added position expression: " + expression); } catch (e) { var errorFile = new File("${path.join(process.env.TEMP || process.env.TMP || '', 'ae_test_error.txt').replace(/\\/g, '\\\\')}"); errorFile.open("w"); errorFile.write("ERROR: " + e.toString()); errorFile.close(); alert("Test failed: " + e.toString()); } `; } // Write the script to a temp file fs.writeFileSync(tempFile, scriptContent); console.error(`Written test script to: ${tempFile}`); // Tell the user what to do return { content: [ { type: "text", text: `I've created a direct test script for the ${params.operation} operation. Please run this script manually in After Effects: 1. In After Effects, go to File > Scripts > Run Script File... 2. Navigate to: ${tempFile} 3. You should see an alert confirming the result. This bypasses the MCP Bridge Auto panel and will directly modify the specified layer.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error creating test script: ${String(error)}` } ], isError: true }; } } ); // --- END NEW TESTING TOOL --- // --- BEGIN NEW EFFECTS TOOLS --- // Add a tool for applying effects to layers server.tool( "apply-effect", "Apply an effect to a layer in After Effects", { compIndex: z.number().int().positive().describe("1-based index of the target composition in the project panel."), layerIndex: z.number().int().positive().describe("1-based index of the target layer within the composition."), effectName: z.string().optional().describe("Display name of the effect to apply (e.g., 'Gaussian Blur')."), effectMatchName: z.string().optional().describe("After Effects internal name for the effect (more reliable, e.g., 'ADBE Gaussian Blur 2')."), effectCategory: z.string().optional().describe("Optional category for filtering effects."), presetPath: z.string().optional().describe("Optional path to an effect preset file (.ffx)."), effectSettings: z.record(z.any()).optional().describe("Optional parameters for the effect (e.g., { 'Blurriness': 25 }).") }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("applyEffect", parameters); return { content: [ { type: "text", text: `Command to apply effect to layer ${parameters.layerIndex} in composition ${parameters.compIndex} has been queued.\n` + `Use the "get-results" tool after a few seconds to check for confirmation.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing apply-effect command: ${String(error)}` } ], isError: true }; } } ); // Add a tool for applying effect templates server.tool( "apply-effect-template", "Apply a predefined effect template to a layer in After Effects", { compIndex: z.number().int().positive().describe("1-based index of the target composition in the project panel."), layerIndex: z.number().int().positive().describe("1-based index of the target layer within the composition."), templateName: z.enum([ "gaussian-blur", "directional-blur", "color-balance", "brightness-contrast", "curves", "glow", "drop-shadow", "cinematic-look", "text-pop" ]).describe("Name of the effect template to apply."), customSettings: z.record(z.any()).optional().describe("Optional custom settings to override defaults.") }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("applyEffectTemplate", parameters); return { content: [ { type: "text", text: `Command to apply effect template '${parameters.templateName}' to layer ${parameters.layerIndex} in composition ${parameters.compIndex} has been queued.\n` + `Use the "get-results" tool after a few seconds to check for confirmation.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing apply-effect-template command: ${String(error)}` } ], isError: true }; } } ); // --- END NEW EFFECTS TOOLS --- // Add direct MCP function for applying effects server.tool( "mcp_aftereffects_applyEffect", "Apply an effect to a layer in After Effects", { compIndex: z.number().int().positive().describe("1-based index of the target composition in the project panel."), layerIndex: z.number().int().positive().describe("1-based index of the target layer within the composition."), effectName: z.string().optional().describe("Display name of the effect to apply (e.g., 'Gaussian Blur')."), effectMatchName: z.string().optional().describe("After Effects internal name for the effect (more reliable, e.g., 'ADBE Gaussian Blur 2')."), effectSettings: z.record(z.any()).optional().describe("Optional parameters for the effect (e.g., { 'Blurriness': 25 }).") }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("applyEffect", parameters); // Wait a bit for After Effects to process the command await new Promise(resolve => setTimeout(resolve, 1000)); // Get the results const result = readResultsFromTempFile(); return { content: [ { type: "text", text: result } ] }; } catch (error) { return { content: [ { type: "text", text: `Error applying effect: ${String(error)}` } ], isError: true }; } } ); // Add direct MCP function for applying effect templates server.tool( "mcp_aftereffects_applyEffectTemplate", "Apply a predefined effect template to a layer in After Effects", { compIndex: z.number().int().positive().describe("1-based index of the target composition in the project panel."), layerIndex: z.number().int().positive().describe("1-based index of the target layer within the composition."), templateName: z.enum([ "gaussian-blur", "directional-blur", "color-balance", "brightness-contrast", "curves", "glow", "drop-shadow", "cinematic-look", "text-pop" ]).describe("Name of the effect template to apply."), customSettings: z.record(z.any()).optional().describe("Optional custom settings to override defaults.") }, async (parameters) => { try { // Queue the command for After Effects writeCommandFile("applyEffectTemplate", parameters); // Wait a bit for After Effects to process the command await new Promise(resolve => setTimeout(resolve, 1000)); // Get the results const result = readResultsFromTempFile(); return { content: [ { type: "text", text: result } ] }; } catch (error) { return { content: [ { type: "text", text: `Error applying effect template: ${String(error)}` } ], isError: true }; } } ); // Update help information to include the new effects tools server.tool( "mcp_aftereffects_get_effects_help", "Get help on using After Effects effects", {}, async () => { return { content: [ { type: "text", text: `# After Effects Effects Help ## Common Effect Match Names These are internal names used by After Effects that can be used with the \`effectMatchName\` parameter: ### Blur & Sharpen - Gaussian Blur: "ADBE Gaussian Blur 2" - Camera Lens Blur: "ADBE Camera Lens Blur" - Directional Blur: "ADBE Motion Blur" - Radial Blur: "ADBE Radial Blur" - Smart Blur: "ADBE Smart Blur" - Unsharp Mask: "ADBE Unsharp Mask" ### Color Correction - Brightness & Contrast: "ADBE Brightness & Contrast 2" - Color Balance: "ADBE Color Balance (HLS)" - Color Balance (RGB): "ADBE Pro Levels2" - Curves: "ADBE CurvesCustom" - Exposure: "ADBE Exposure2" - Hue/Saturation: "ADBE HUE SATURATION" - Levels: "ADBE Pro Levels2" - Vibrance: "ADBE Vibrance" ### Stylistic - Glow: "ADBE Glow" - Drop Shadow: "ADBE Drop Shadow" - Bevel Alpha: "ADBE Bevel Alpha" - Noise: "ADBE Noise" - Fractal Noise: "ADBE Fractal Noise" - CC Particle World: "CC Particle World" - CC Light Sweep: "CC Light Sweep" ## Effect Templates The following predefined effect templates are available: - \`gaussian-blur\`: Simple Gaussian blur effect - \`directional-blur\`: Motion blur in a specific direction - \`color-balance\`: Adjust hue, lightness, and saturation - \`brightness-contrast\`: Basic brightness and contrast adjustment - \`curves\`: Advanced color adjustment using curves - \`glow\`: Add a glow effect to elements - \`drop-shadow\`: Add a customizable drop shadow - \`cinematic-look\`: Combination of effects for a cinematic appearance - \`text-pop\`: Effects to make text stand out (glow and shadow) ## Example Usage To apply a Gaussian blur effect: \`\`\`json { "compIndex": 1, "layerIndex": 1, "effectMatchName": "ADBE Gaussian Blur 2", "effectSettings": { "Blurriness": 25 } } \`\`\` To apply the "cinematic-look" template: \`\`\`json { "compIndex": 1, "layerIndex": 1, "templateName": "cinematic-look" } \`\`\` ` } ] }; } ); // Add a direct tool for our bridge test effects server.tool( "run-bridge-test", "Run the bridge test effects script to verify communication and apply test effects", {}, async () => { try { // Clear any stale result data clearResultsFile(); // Write command to file for After Effects to pick up writeCommandFile("bridgeTestEffects", {}); return { content: [ { type: "text", text: `Bridge test effects command has been queued.\n` + `Please ensure the "MCP Bridge Auto" panel is open in After Effects.\n` + `Use the "get-results" tool after a few seconds to check for the test results.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error queuing bridge test command: ${String(error)}` } ], isError: true }; } } ); // Start the MCP server async function main() { console.error("After Effects MCP Server starting..."); console.error(`Scripts directory: ${SCRIPTS_DIR}`); console.error(`Temp directory: ${TEMP_DIR}`); const transport = new StdioServerTransport(); await server.connect(transport); console.error("After Effects MCP Server running..."); } main().catch(error => { console.error("Fatal error:", error); process.exit(1); });

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/Dakkshin/after-effects-mcp'

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