Skip to main content
Glama
index.ts68.9 kB
/** * MCP Tools for Adobe Premiere Pro * * This module provides tools that can be called by AI agents to perform * various video editing operations in Adobe Premiere Pro. */ import { z } from 'zod'; import { PremiereProBridge } from '../bridge/index.js'; import { Logger } from '../utils/logger.js'; export interface MCPTool { name: string; description: string; inputSchema: z.ZodSchema<any>; } export class PremiereProTools { private bridge: PremiereProBridge; private logger: Logger; constructor(bridge: PremiereProBridge) { this.bridge = bridge; this.logger = new Logger('PremiereProTools'); } getAvailableTools(): MCPTool[] { return [ // Discovery Tools (NEW) { name: 'list_project_items', description: 'Lists all media items, bins, and assets in the current Premiere Pro project. Use this to discover available media before performing operations.', inputSchema: z.object({ includeBins: z.boolean().optional().describe('Whether to include bin information in the results'), includeMetadata: z.boolean().optional().describe('Whether to include detailed metadata for each item') }) }, { name: 'list_sequences', description: 'Lists all sequences in the current Premiere Pro project with their IDs, names, and basic properties.', inputSchema: z.object({}) }, { name: 'list_sequence_tracks', description: 'Lists all video and audio tracks in a specific sequence with their properties and clips.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence to list tracks for') }) }, { name: 'get_project_info', description: 'Gets comprehensive information about the current project including name, path, settings, and status.', inputSchema: z.object({}) }, // Project Management { name: 'create_project', description: 'Creates a new Adobe Premiere Pro project. Use this when the user wants to start a new video editing project from scratch.', inputSchema: z.object({ name: z.string().describe('The name for the new project, e.g., "My Summer Vacation"'), location: z.string().describe('The absolute directory path where the project file should be saved, e.g., "/Users/user/Documents/Videos"') }) }, { name: 'open_project', description: 'Opens an existing Adobe Premiere Pro project from a specified file path.', inputSchema: z.object({ path: z.string().describe('The absolute path to the .prproj file to open') }) }, { name: 'save_project', description: 'Saves the currently active Adobe Premiere Pro project.', inputSchema: z.object({}) }, { name: 'save_project_as', description: 'Saves the current project with a new name and location.', inputSchema: z.object({ name: z.string().describe('The new name for the project'), location: z.string().describe('The absolute directory path where the project should be saved') }) }, // Media Management { name: 'import_media', description: 'Imports a media file (video, audio, image) into the current Premiere Pro project.', inputSchema: z.object({ filePath: z.string().describe('The absolute path to the media file to import'), binName: z.string().optional().describe('The name of the bin to import the media into. If not provided, it will be imported into the root.') }) }, { name: 'import_folder', description: 'Imports all media files from a folder into the current Premiere Pro project.', inputSchema: z.object({ folderPath: z.string().describe('The absolute path to the folder containing media files'), binName: z.string().optional().describe('The name of the bin to import the media into'), recursive: z.boolean().optional().describe('Whether to import from subfolders recursively') }) }, { name: 'create_bin', description: 'Creates a new bin (folder) in the project panel to organize media.', inputSchema: z.object({ name: z.string().describe('The name for the new bin'), parentBinName: z.string().optional().describe('The name of the parent bin to create this bin inside') }) }, // Sequence Management { name: 'create_sequence', description: 'Creates a new sequence in the project. A sequence is a timeline where you edit clips.', inputSchema: z.object({ name: z.string().describe('The name for the new sequence'), presetPath: z.string().optional().describe('Optional path to a sequence preset file for custom settings'), width: z.number().optional().describe('Sequence width in pixels'), height: z.number().optional().describe('Sequence height in pixels'), frameRate: z.number().optional().describe('Frame rate (e.g., 24, 25, 30, 60)'), sampleRate: z.number().optional().describe('Audio sample rate (e.g., 48000)') }) }, { name: 'duplicate_sequence', description: 'Creates a copy of an existing sequence with a new name.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence to duplicate'), newName: z.string().describe('The name for the new sequence copy') }) }, { name: 'delete_sequence', description: 'Deletes a sequence from the project.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence to delete') }) }, // Timeline Operations { name: 'add_to_timeline', description: 'Adds a media clip from the project panel to a sequence timeline at a specific track and time.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence (timeline) to add the clip to'), projectItemId: z.string().describe('The ID of the project item (clip) to add'), trackIndex: z.number().describe('The index of the video or audio track (0-based)'), time: z.number().describe('The time in seconds where the clip should be placed on the timeline'), insertMode: z.enum(['overwrite', 'insert']).optional().describe('Whether to overwrite existing content or insert and shift') }) }, { name: 'remove_from_timeline', description: 'Removes a clip from the timeline.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip on the timeline to remove'), deleteMode: z.enum(['ripple', 'lift']).optional().describe('Whether to ripple delete (close gap) or lift (leave gap)') }) }, { name: 'move_clip', description: 'Moves a clip to a different position on the timeline.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip to move'), newTime: z.number().describe('The new time position in seconds'), newTrackIndex: z.number().optional().describe('The new track index (if moving to different track)') }) }, { name: 'trim_clip', description: 'Adjusts the in and out points of a clip on the timeline, effectively shortening it.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip on the timeline to trim'), inPoint: z.number().optional().describe('The new in point in seconds from the start of the clip'), outPoint: z.number().optional().describe('The new out point in seconds from the start of the clip'), duration: z.number().optional().describe('Alternative: set the desired duration in seconds') }) }, { name: 'split_clip', description: 'Splits a clip at a specific time point, creating two separate clips.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip to split'), splitTime: z.number().describe('The time in seconds where to split the clip') }) }, // Effects and Transitions { name: 'apply_effect', description: 'Applies a visual or audio effect to a specific clip on the timeline.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip to apply the effect to'), effectName: z.string().describe('The name of the effect to apply (e.g., "Gaussian Blur", "Lumetri Color")'), parameters: z.record(z.any()).optional().describe('Key-value pairs for the effect\'s parameters') }) }, { name: 'remove_effect', description: 'Removes an effect from a clip.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip'), effectName: z.string().describe('The name of the effect to remove') }) }, { name: 'add_transition', description: 'Adds a transition (e.g., cross dissolve) between two adjacent clips on the timeline.', inputSchema: z.object({ clipId1: z.string().describe('The ID of the first clip (outgoing)'), clipId2: z.string().describe('The ID of the second clip (incoming)'), transitionName: z.string().describe('The name of the transition to add (e.g., "Cross Dissolve")'), duration: z.number().describe('The duration of the transition in seconds') }) }, { name: 'add_transition_to_clip', description: 'Adds a transition to the beginning or end of a single clip.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip'), transitionName: z.string().describe('The name of the transition'), position: z.enum(['start', 'end']).describe('Whether to add the transition at the start or end of the clip'), duration: z.number().describe('The duration of the transition in seconds') }) }, // Audio Operations { name: 'adjust_audio_levels', description: 'Adjusts the volume (gain) of an audio clip on the timeline.', inputSchema: z.object({ clipId: z.string().describe('The ID of the audio clip to adjust'), level: z.number().describe('The new audio level in decibels (dB). Can be positive or negative.') }) }, { name: 'add_audio_keyframes', description: 'Adds keyframes to audio levels for dynamic volume changes.', inputSchema: z.object({ clipId: z.string().describe('The ID of the audio clip'), keyframes: z.array(z.object({ time: z.number().describe('Time in seconds'), level: z.number().describe('Audio level in dB') })).describe('Array of keyframe data') }) }, { name: 'mute_track', description: 'Mutes or unmutes an entire audio track.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence'), trackIndex: z.number().describe('The index of the audio track'), muted: z.boolean().describe('Whether to mute (true) or unmute (false) the track') }) }, // Text and Graphics { name: 'add_text_overlay', description: 'Adds a text layer (title) over the video timeline.', inputSchema: z.object({ text: z.string().describe('The text content to display'), sequenceId: z.string().describe('The sequence to add the text to'), trackIndex: z.number().describe('The video track to place the text on'), startTime: z.number().describe('The time in seconds when the text should appear'), duration: z.number().describe('How long the text should remain on screen in seconds'), fontFamily: z.string().optional().describe('e.g., "Arial", "Times New Roman"'), fontSize: z.number().optional().describe('e.g., 48'), color: z.string().optional().describe('The hex color code for the text, e.g., "#FFFFFF"'), position: z.object({ x: z.number().optional().describe('Horizontal position (0-100)'), y: z.number().optional().describe('Vertical position (0-100)') }).optional().describe('Text position on screen'), alignment: z.enum(['left', 'center', 'right']).optional().describe('Text alignment') }) }, { name: 'add_shape', description: 'Adds a shape (rectangle, circle, etc.) to the timeline.', inputSchema: z.object({ shapeType: z.enum(['rectangle', 'circle', 'triangle']).describe('The type of shape to add'), sequenceId: z.string().describe('The sequence to add the shape to'), trackIndex: z.number().describe('The video track to place the shape on'), startTime: z.number().describe('The time in seconds when the shape should appear'), duration: z.number().describe('How long the shape should remain on screen in seconds'), color: z.string().optional().describe('The hex color code for the shape'), size: z.object({ width: z.number().optional().describe('Width in pixels'), height: z.number().optional().describe('Height in pixels') }).optional().describe('Shape size'), position: z.object({ x: z.number().optional().describe('Horizontal position (0-100)'), y: z.number().optional().describe('Vertical position (0-100)') }).optional().describe('Shape position on screen') }) }, // Color Correction { name: 'color_correct', description: 'Applies basic color correction adjustments to a video clip.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip to color correct'), brightness: z.number().optional().describe('Brightness adjustment (-100 to 100)'), contrast: z.number().optional().describe('Contrast adjustment (-100 to 100)'), saturation: z.number().optional().describe('Saturation adjustment (-100 to 100)'), hue: z.number().optional().describe('Hue adjustment in degrees (-180 to 180)'), highlights: z.number().optional().describe('Adjustment for the brightest parts of the image (-100 to 100)'), shadows: z.number().optional().describe('Adjustment for the darkest parts of the image (-100 to 100)'), temperature: z.number().optional().describe('Color temperature adjustment (-100 to 100)'), tint: z.number().optional().describe('Tint adjustment (-100 to 100)') }) }, { name: 'apply_lut', description: 'Applies a Look-Up Table (LUT) to a clip for color grading.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip'), lutPath: z.string().describe('The absolute path to the .cube or .3dl LUT file'), intensity: z.number().optional().describe('LUT intensity (0-100)') }) }, // Export and Rendering { name: 'export_sequence', description: 'Renders and exports a sequence to a video file. This is for creating the final video.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence to export'), outputPath: z.string().describe('The absolute path where the final video file will be saved'), presetPath: z.string().optional().describe('Optional path to an export preset file (.epr) for specific settings'), format: z.enum(['mp4', 'mov', 'avi', 'h264', 'prores']).optional().describe('The export format or codec'), quality: z.enum(['low', 'medium', 'high', 'maximum']).optional().describe('Export quality setting'), resolution: z.string().optional().describe('Export resolution (e.g., "1920x1080", "3840x2160")') }) }, { name: 'export_frame', description: 'Exports a single frame from a sequence as an image file.', inputSchema: z.object({ sequenceId: z.string().describe('The ID of the sequence'), time: z.number().describe('The time in seconds to export the frame from'), outputPath: z.string().describe('The absolute path where the image file will be saved'), format: z.enum(['png', 'jpg', 'tiff']).optional().describe('The image format') }) }, // Advanced Features { name: 'create_multicam_sequence', description: 'Creates a multicamera source sequence from multiple video clips, synchronized by audio or timecode.', inputSchema: z.object({ name: z.string().describe('The name for the new multicam sequence'), cameraFiles: z.array(z.string()).describe('An array of absolute file paths for each camera angle'), syncMethod: z.enum(['timecode', 'audio', 'markers']).describe('The method to use for synchronizing the clips') }) }, { name: 'create_proxy_media', description: 'Generates low-resolution proxy versions of high-resolution media to improve editing performance.', inputSchema: z.object({ projectItemIds: z.array(z.string()).describe('An array of IDs of the project items to create proxies for'), proxyPreset: z.string().describe('The name of the proxy preset to use'), replaceOriginals: z.boolean().optional().describe('Whether to replace original media with proxies') }) }, { name: 'auto_edit_to_music', description: 'Automatically creates an edit by cutting video clips to the beat of a music track.', inputSchema: z.object({ audioTrackId: z.string().describe('The ID of the audio track containing the music'), videoClipIds: z.array(z.string()).describe('An array of video clip IDs to use for the edit'), editStyle: z.enum(['cuts_only', 'cuts_and_transitions', 'beat_sync']).describe('The desired editing style'), sensitivity: z.number().optional().describe('Beat detection sensitivity (0-100)') }) }, { name: 'stabilize_clip', description: 'Applies video stabilization to reduce camera shake.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip to stabilize'), method: z.enum(['warp', 'subspace']).optional().describe('Stabilization method'), smoothness: z.number().optional().describe('Stabilization smoothness (0-100)') }) }, { name: 'speed_change', description: 'Changes the playback speed of a clip.', inputSchema: z.object({ clipId: z.string().describe('The ID of the clip'), speed: z.number().describe('Speed multiplier (0.1 = 10% speed, 2.0 = 200% speed)'), maintainAudio: z.boolean().optional().describe('Whether to maintain audio pitch when changing speed') }) } ]; } async executeTool(name: string, args: Record<string, any>): Promise<any> { const tool = this.getAvailableTools().find(t => t.name === name); if (!tool) { return { success: false, error: `Tool '${name}' not found`, availableTools: this.getAvailableTools().map(t => t.name) }; } // Validate input arguments try { tool.inputSchema.parse(args); } catch (error) { return { success: false, error: `Invalid arguments for tool '${name}': ${error}`, expectedSchema: tool.inputSchema.description }; } this.logger.info(`Executing tool: ${name} with args:`, args); try { switch (name) { // Discovery Tools case 'list_project_items': return await this.listProjectItems(args.includeBins, args.includeMetadata); case 'list_sequences': return await this.listSequences(); case 'list_sequence_tracks': return await this.listSequenceTracks(args.sequenceId); case 'get_project_info': return await this.getProjectInfo(); // Project Management case 'create_project': return await this.createProject(args.name, args.location); case 'open_project': return await this.openProject(args.path); case 'save_project': return await this.saveProject(); case 'save_project_as': return await this.saveProjectAs(args.name, args.location); // Media Management case 'import_media': return await this.importMedia(args.filePath, args.binName); case 'import_folder': return await this.importFolder(args.folderPath, args.binName, args.recursive); case 'create_bin': return await this.createBin(args.name, args.parentBinName); // Sequence Management case 'create_sequence': return await this.createSequence(args.name, args.presetPath, args.width, args.height, args.frameRate, args.sampleRate); case 'duplicate_sequence': return await this.duplicateSequence(args.sequenceId, args.newName); case 'delete_sequence': return await this.deleteSequence(args.sequenceId); // Timeline Operations case 'add_to_timeline': return await this.addToTimeline(args.sequenceId, args.projectItemId, args.trackIndex, args.time, args.insertMode); case 'remove_from_timeline': return await this.removeFromTimeline(args.clipId, args.deleteMode); case 'move_clip': return await this.moveClip(args.clipId, args.newTime, args.newTrackIndex); case 'trim_clip': return await this.trimClip(args.clipId, args.inPoint, args.outPoint, args.duration); case 'split_clip': return await this.splitClip(args.clipId, args.splitTime); // Effects and Transitions case 'apply_effect': return await this.applyEffect(args.clipId, args.effectName, args.parameters); case 'remove_effect': return await this.removeEffect(args.clipId, args.effectName); case 'add_transition': return await this.addTransition(args.clipId1, args.clipId2, args.transitionName, args.duration); case 'add_transition_to_clip': return await this.addTransitionToClip(args.clipId, args.transitionName, args.position, args.duration); // Audio Operations case 'adjust_audio_levels': return await this.adjustAudioLevels(args.clipId, args.level); case 'add_audio_keyframes': return await this.addAudioKeyframes(args.clipId, args.keyframes); case 'mute_track': return await this.muteTrack(args.sequenceId, args.trackIndex, args.muted); // Text and Graphics case 'add_text_overlay': return await this.addTextOverlay(args); case 'add_shape': return await this.addShape(args); // Color Correction case 'color_correct': return await this.colorCorrect(args.clipId, args); case 'apply_lut': return await this.applyLut(args.clipId, args.lutPath, args.intensity); // Export and Rendering case 'export_sequence': return await this.exportSequence(args.sequenceId, args.outputPath, args.presetPath, args.format, args.quality, args.resolution); case 'export_frame': return await this.exportFrame(args.sequenceId, args.time, args.outputPath, args.format); // Advanced Features case 'create_multicam_sequence': return await this.createMulticamSequence(args.name, args.cameraFiles, args.syncMethod); case 'create_proxy_media': return await this.createProxyMedia(args.projectItemIds, args.proxyPreset, args.replaceOriginals); case 'auto_edit_to_music': return await this.autoEditToMusic(args.audioTrackId, args.videoClipIds, args.editStyle, args.sensitivity); case 'stabilize_clip': return await this.stabilizeClip(args.clipId, args.method, args.smoothness); case 'speed_change': return await this.speedChange(args.clipId, args.speed, args.maintainAudio); default: return { success: false, error: `Tool '${name}' not implemented`, availableTools: this.getAvailableTools().map(t => t.name) }; } } catch (error) { this.logger.error(`Error executing tool ${name}:`, error); return { success: false, error: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`, tool: name, args: args }; } } // Discovery Tools Implementation private async listProjectItems(includeBins = true, includeMetadata = false): Promise<any> { const script = ` try { var items = []; var bins = []; // List all project items for (var i = 0; i < app.project.rootItem.children.numItems; i++) { var item = app.project.rootItem.children[i]; var itemInfo = { id: item.nodeId, name: item.name, type: item.type.toString(), path: item.getMediaPath(), duration: item.duration ? item.duration.seconds : null }; if (${includeMetadata}) { itemInfo.metadata = { width: item.getMediaWidth ? item.getMediaWidth() : null, height: item.getMediaHeight ? item.getMediaHeight() : null, frameRate: item.getMediaFrameRate ? item.getMediaFrameRate() : null, hasVideo: item.hasVideo ? item.hasVideo() : false, hasAudio: item.hasAudio ? item.hasAudio() : false }; } if (item.type === ProjectItemType.BIN) { bins.push(itemInfo); } else { items.push(itemInfo); } } JSON.stringify({ success: true, items: items, bins: ${includeBins} ? bins : [], totalItems: items.length, totalBins: bins.length }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async listSequences(): Promise<any> { const script = ` try { var sequences = []; for (var i = 0; i < app.project.sequences.numSequences; i++) { var seq = app.project.sequences[i]; sequences.push({ id: seq.sequenceID, name: seq.name, duration: seq.duration.seconds, width: seq.frameBounds.width, height: seq.frameBounds.height, frameRate: seq.frameRate, videoTrackCount: seq.videoTracks.numTracks, audioTrackCount: seq.audioTracks.numTracks }); } JSON.stringify({ success: true, sequences: sequences, count: sequences.length }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async listSequenceTracks(sequenceId: string): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var videoTracks = []; var audioTracks = []; // List video tracks for (var i = 0; i < sequence.videoTracks.numTracks; i++) { var track = sequence.videoTracks[i]; var clips = []; for (var j = 0; j < track.clips.numItems; j++) { var clip = track.clips[j]; clips.push({ id: clip.nodeId, name: clip.name, startTime: clip.start.seconds, endTime: clip.end.seconds, duration: clip.duration.seconds }); } videoTracks.push({ index: i, name: track.name || "Video " + (i + 1), enabled: track.isTargeted(), locked: track.isLocked(), clips: clips, clipCount: clips.length }); } // List audio tracks for (var i = 0; i < sequence.audioTracks.numTracks; i++) { var track = sequence.audioTracks[i]; var clips = []; for (var j = 0; j < track.clips.numItems; j++) { var clip = track.clips[j]; clips.push({ id: clip.nodeId, name: clip.name, startTime: clip.start.seconds, endTime: clip.end.seconds, duration: clip.duration.seconds }); } audioTracks.push({ index: i, name: track.name || "Audio " + (i + 1), enabled: track.isTargeted(), locked: track.isLocked(), clips: clips, clipCount: clips.length }); } JSON.stringify({ success: true, sequenceId: "${sequenceId}", sequenceName: sequence.name, videoTracks: videoTracks, audioTracks: audioTracks, totalVideoTracks: videoTracks.length, totalAudioTracks: audioTracks.length }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async getProjectInfo(): Promise<any> { const script = ` try { var project = app.project; JSON.stringify({ success: true, name: project.name, path: project.path, activeSequence: project.activeSequence ? { id: project.activeSequence.sequenceID, name: project.activeSequence.name } : null, itemCount: project.rootItem.children.numItems, sequenceCount: project.sequences.numSequences, isDirty: project.dirty, hasActiveSequence: project.activeSequence !== null }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Project Management Implementation private async createProject(name: string, location: string): Promise<any> { try { const result = await this.bridge.createProject(name, location); return { success: true, message: `Project "${name}" created successfully`, projectPath: `${location}/${name}.prproj`, ...result }; } catch (error) { return { success: false, error: `Failed to create project: ${error instanceof Error ? error.message : String(error)}` }; } } private async openProject(path: string): Promise<any> { try { const result = await this.bridge.openProject(path); return { success: true, message: `Project opened successfully`, projectPath: path, ...result }; } catch (error) { return { success: false, error: `Failed to open project: ${error instanceof Error ? error.message : String(error)}` }; } } private async saveProject(): Promise<any> { try { await this.bridge.saveProject(); return { success: true, message: 'Project saved successfully', timestamp: new Date().toISOString() }; } catch (error) { return { success: false, error: `Failed to save project: ${error instanceof Error ? error.message : String(error)}` }; } } private async saveProjectAs(name: string, location: string): Promise<any> { const script = ` try { var project = app.project; var newPath = "${location}/${name}.prproj"; project.saveAs(newPath); JSON.stringify({ success: true, message: "Project saved as: " + newPath, newPath: newPath }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Media Management Implementation private async importMedia(filePath: string, binName?: string): Promise<any> { try { const result = await this.bridge.importMedia(filePath); return { success: true, message: `Media imported successfully`, filePath: filePath, binName: binName || 'Root', ...result }; } catch (error) { return { success: false, error: `Failed to import media: ${error instanceof Error ? error.message : String(error)}`, filePath: filePath }; } } private async importFolder(folderPath: string, binName?: string, recursive = false): Promise<any> { const script = ` try { var folder = new Folder("${folderPath}"); var importedItems = []; var errors = []; function importFiles(dir, targetBin) { var files = dir.getFiles(); for (var i = 0; i < files.length; i++) { var file = files[i]; if (file instanceof File) { try { var item = targetBin.importFiles([file.fsName]); if (item && item.length > 0) { importedItems.push({ name: file.name, path: file.fsName, id: item[0].nodeId }); } } catch (e) { errors.push({ file: file.name, error: e.toString() }); } } else if (file instanceof Folder && ${recursive}) { importFiles(file, targetBin); } } } var targetBin = app.project.rootItem; ${binName ? `targetBin = app.project.rootItem.children["${binName}"] || app.project.rootItem;` : ''} importFiles(folder, targetBin); JSON.stringify({ success: true, importedItems: importedItems, errors: errors, totalImported: importedItems.length, totalErrors: errors.length }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async createBin(name: string, parentBinName?: string): Promise<any> { const script = ` try { var parentBin = app.project.rootItem; ${parentBinName ? `parentBin = app.project.rootItem.children["${parentBinName}"] || app.project.rootItem;` : ''} var newBin = parentBin.createBin("${name}"); JSON.stringify({ success: true, binName: "${name}", binId: newBin.nodeId, parentBin: parentBinName || "Root" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Sequence Management Implementation private async createSequence(name: string, presetPath?: string, _width?: number, _height?: number, _frameRate?: number, _sampleRate?: number): Promise<any> { try { const result = await this.bridge.createSequence(name, presetPath); return { success: true, message: `Sequence "${name}" created successfully`, sequenceName: name, ...result }; } catch (error) { return { success: false, error: `Failed to create sequence: ${error instanceof Error ? error.message : String(error)}`, sequenceName: name }; } } private async duplicateSequence(sequenceId: string, newName: string): Promise<any> { const script = ` try { var originalSeq = app.project.getSequenceByID("${sequenceId}"); if (!originalSeq) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var newSeq = originalSeq.clone(); newSeq.name = "${newName}"; JSON.stringify({ success: true, originalSequenceId: "${sequenceId}", newSequenceId: newSeq.sequenceID, newName: "${newName}" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async deleteSequence(sequenceId: string): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var sequenceName = sequence.name; app.project.deleteSequence(sequence); JSON.stringify({ success: true, message: "Sequence deleted successfully", deletedSequenceId: "${sequenceId}", deletedSequenceName: sequenceName }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Timeline Operations Implementation private async addToTimeline(sequenceId: string, projectItemId: string, trackIndex: number, time: number, insertMode = 'overwrite'): Promise<any> { try { const result = await this.bridge.addToTimeline(sequenceId, projectItemId, trackIndex, time); return { success: true, message: `Clip added to timeline successfully`, sequenceId: sequenceId, projectItemId: projectItemId, trackIndex: trackIndex, time: time, insertMode: insertMode, ...result }; } catch (error) { return { success: false, error: `Failed to add clip to timeline: ${error instanceof Error ? error.message : String(error)}`, sequenceId: sequenceId, projectItemId: projectItemId, trackIndex: trackIndex, time: time }; } } private async removeFromTimeline(clipId: string, deleteMode = 'ripple'): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var clipName = clip.name; var track = clip.getTrack(); if ("${deleteMode}" === "ripple") { track.removeClip(clip, true); // ripple delete } else { track.removeClip(clip, false); // lift delete } JSON.stringify({ success: true, message: "Clip removed from timeline", clipId: "${clipId}", clipName: clipName, deleteMode: "${deleteMode}" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async moveClip(clipId: string, newTime: number, newTrackIndex?: number): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var oldTime = clip.start.seconds; var oldTrack = clip.getTrack(); var oldTrackIndex = oldTrack.index; clip.start = new Time("${newTime}s"); ${newTrackIndex !== undefined ? ` var newTrack = app.project.activeSequence.videoTracks[${newTrackIndex}]; if (newTrack) { oldTrack.removeClip(clip, false); newTrack.insertClip(clip, new Time("${newTime}s")); } ` : ''} JSON.stringify({ success: true, message: "Clip moved successfully", clipId: "${clipId}", oldTime: oldTime, newTime: ${newTime}, oldTrackIndex: oldTrackIndex, newTrackIndex: ${newTrackIndex !== undefined ? newTrackIndex : 'unchanged'} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async trimClip(clipId: string, inPoint?: number, outPoint?: number, duration?: number): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var oldInPoint = clip.inPoint.seconds; var oldOutPoint = clip.outPoint.seconds; var oldDuration = clip.duration.seconds; ${inPoint !== undefined ? `clip.inPoint = new Time("${inPoint}s");` : ''} ${outPoint !== undefined ? `clip.outPoint = new Time("${outPoint}s");` : ''} ${duration !== undefined ? `clip.outPoint = new Time(clip.inPoint.seconds + ${duration});` : ''} JSON.stringify({ success: true, message: "Clip trimmed successfully", clipId: "${clipId}", oldInPoint: oldInPoint, oldOutPoint: oldOutPoint, oldDuration: oldDuration, newInPoint: clip.inPoint.seconds, newOutPoint: clip.outPoint.seconds, newDuration: clip.duration.seconds }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async splitClip(clipId: string, splitTime: number): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var track = clip.getTrack(); var splitPoint = new Time(clip.start.seconds + ${splitTime}); var newClip = track.splitClip(clip, splitPoint); JSON.stringify({ success: true, message: "Clip split successfully", originalClipId: "${clipId}", newClipId: newClip.nodeId, splitTime: ${splitTime}, splitPoint: splitPoint.seconds }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Effects and Transitions Implementation private async applyEffect(clipId: string, effectName: string, parameters?: Record<string, any>): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var effect = clip.addEffect("${effectName}"); if (!effect) { JSON.stringify({ success: false, error: "Effect not found or could not be applied" }); return; } ${parameters ? Object.entries(parameters).map(([key, value]) => `try { if (effect.properties["${key}"]) effect.properties["${key}"].setValue(${JSON.stringify(value)}); } catch (e) { /* Parameter not found */ }` ).join('\n') : ''} JSON.stringify({ success: true, message: "Effect applied successfully", clipId: "${clipId}", effectName: "${effectName}", effectId: effect.matchName, parametersApplied: ${parameters ? Object.keys(parameters).length : 0} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async removeEffect(clipId: string, effectName: string): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var effects = clip.getEffects(); var removed = false; for (var i = 0; i < effects.length; i++) { if (effects[i].displayName === "${effectName}") { clip.removeEffect(effects[i]); removed = true; break; } } if (!removed) { JSON.stringify({ success: false, error: "Effect not found on clip" }); return; } JSON.stringify({ success: true, message: "Effect removed successfully", clipId: "${clipId}", effectName: "${effectName}" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async addTransition(clipId1: string, clipId2: string, transitionName: string, duration: number): Promise<any> { const script = ` try { var clip1 = app.project.getClipByID("${clipId1}"); var clip2 = app.project.getClipByID("${clipId2}"); if (!clip1 || !clip2) { JSON.stringify({ success: false, error: "One or both clips not found" }); return; } var track = clip1.getTrack(); var transition = track.addTransition("${transitionName}", clip1, clip2, ${duration}); if (!transition) { JSON.stringify({ success: false, error: "Failed to add transition" }); return; } JSON.stringify({ success: true, message: "Transition added successfully", transitionName: "${transitionName}", duration: ${duration}, clip1Id: "${clipId1}", clip2Id: "${clipId2}", transitionId: transition.nodeId }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async addTransitionToClip(clipId: string, transitionName: string, position: 'start' | 'end', duration: number): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var track = clip.getTrack(); var transition; if ("${position}" === "start") { transition = track.addTransition("${transitionName}", clip, "start", ${duration}); } else { transition = track.addTransition("${transitionName}", clip, "end", ${duration}); } if (!transition) { JSON.stringify({ success: false, error: "Failed to add transition" }); return; } JSON.stringify({ success: true, message: "Transition added successfully", transitionName: "${transitionName}", position: "${position}", duration: ${duration}, clipId: "${clipId}", transitionId: transition.nodeId }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Audio Operations Implementation private async adjustAudioLevels(clipId: string, level: number): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var audioComponent = clip.components[0]; if (!audioComponent || !audioComponent.properties["Volume"]) { JSON.stringify({ success: false, error: "Audio component not found or clip has no audio" }); return; } var oldLevel = audioComponent.properties["Volume"].getValue(); audioComponent.properties["Volume"].setValue(${level}); JSON.stringify({ success: true, message: "Audio level adjusted successfully", clipId: "${clipId}", oldLevel: oldLevel, newLevel: ${level} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async addAudioKeyframes(clipId: string, keyframes: Array<{time: number, level: number}>): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var audioComponent = clip.components[0]; if (!audioComponent || !audioComponent.properties["Volume"]) { JSON.stringify({ success: false, error: "Audio component not found or clip has no audio" }); return; } var volumeProperty = audioComponent.properties["Volume"]; var addedKeyframes = []; ${keyframes.map(kf => ` try { volumeProperty.addKey(new Time("${kf.time}s")); volumeProperty.setValueAtKey(new Time("${kf.time}s"), ${kf.level}); addedKeyframes.push({ time: ${kf.time}, level: ${kf.level} }); } catch (e) { // Keyframe already exists or invalid time } `).join('\n')} JSON.stringify({ success: true, message: "Audio keyframes added successfully", clipId: "${clipId}", addedKeyframes: addedKeyframes, totalKeyframes: addedKeyframes.length }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async muteTrack(sequenceId: string, trackIndex: number, muted: boolean): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var track = sequence.audioTracks[${trackIndex}]; if (!track) { JSON.stringify({ success: false, error: "Audio track not found" }); return; } track.setMute(${muted}); JSON.stringify({ success: true, message: "Track mute status changed successfully", sequenceId: "${sequenceId}", trackIndex: ${trackIndex}, muted: ${muted} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Text and Graphics Implementation private async addTextOverlay(args: any): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${args.sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var track = sequence.videoTracks[${args.trackIndex}]; if (!track) { JSON.stringify({ success: false, error: "Video track not found" }); return; } // Create a text clip using the legacy title system var titleItem = app.project.createNewTitle("${args.text}"); if (!titleItem) { JSON.stringify({ success: false, error: "Failed to create title" }); return; } // Set text properties using the legacy title API var title = titleItem.getText(); if (title) { title.text = "${args.text}"; ${args.fontFamily ? `title.fontFamily = "${args.fontFamily}";` : ''} ${args.fontSize ? `title.fontSize = ${args.fontSize};` : ''} ${args.color ? `title.fillColor = "${args.color}";` : ''} ${args.position ? ` title.horizontalJustification = "${args.alignment || 'center'}"; title.verticalJustification = "center"; ` : ''} } // Insert the title into the timeline var titleClip = track.insertClip(titleItem, new Time("${args.startTime}s")); titleClip.end = new Time(titleClip.start.seconds + ${args.duration}); JSON.stringify({ success: true, message: "Text overlay added successfully", text: "${args.text}", clipId: titleClip.nodeId, startTime: ${args.startTime}, duration: ${args.duration}, trackIndex: ${args.trackIndex} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async addShape(args: any): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${args.sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } var track = sequence.videoTracks[${args.trackIndex}]; if (!track) { JSON.stringify({ success: false, error: "Video track not found" }); return; } // Create a shape using the legacy title system var shapeItem = app.project.createNewTitle("Shape"); if (!shapeItem) { JSON.stringify({ success: false, error: "Failed to create shape" }); return; } // Add shape to title var shape = shapeItem.addShape("${args.shapeType}"); if (shape) { ${args.color ? `shape.fillColor = "${args.color}";` : ''} ${args.size ? ` shape.width = ${args.size.width || 100}; shape.height = ${args.size.height || 100}; ` : ''} ${args.position ? ` shape.x = ${args.position.x || 50}; shape.y = ${args.position.y || 50}; ` : ''} } // Insert the shape into the timeline var shapeClip = track.insertClip(shapeItem, new Time("${args.startTime}s")); shapeClip.end = new Time(shapeClip.start.seconds + ${args.duration}); JSON.stringify({ success: true, message: "Shape added successfully", shapeType: "${args.shapeType}", clipId: shapeClip.nodeId, startTime: ${args.startTime}, duration: ${args.duration}, trackIndex: ${args.trackIndex} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Color Correction Implementation private async colorCorrect(clipId: string, adjustments: any): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var colorCorrection = clip.addEffect("Lumetri Color"); if (!colorCorrection) { JSON.stringify({ success: false, error: "Failed to add color correction effect" }); return; } ${adjustments.brightness !== undefined ? `try { colorCorrection.properties["Brightness"].setValue(${adjustments.brightness}); } catch (e) {}` : ''} ${adjustments.contrast !== undefined ? `try { colorCorrection.properties["Contrast"].setValue(${adjustments.contrast}); } catch (e) {}` : ''} ${adjustments.saturation !== undefined ? `try { colorCorrection.properties["Saturation"].setValue(${adjustments.saturation}); } catch (e) {}` : ''} ${adjustments.hue !== undefined ? `try { colorCorrection.properties["Hue"].setValue(${adjustments.hue}); } catch (e) {}` : ''} ${adjustments.highlights !== undefined ? `try { colorCorrection.properties["Highlights"].setValue(${adjustments.highlights}); } catch (e) {}` : ''} ${adjustments.shadows !== undefined ? `try { colorCorrection.properties["Shadows"].setValue(${adjustments.shadows}); } catch (e) {}` : ''} ${adjustments.temperature !== undefined ? `try { colorCorrection.properties["Temperature"].setValue(${adjustments.temperature}); } catch (e) {}` : ''} ${adjustments.tint !== undefined ? `try { colorCorrection.properties["Tint"].setValue(${adjustments.tint}); } catch (e) {}` : ''} JSON.stringify({ success: true, message: "Color correction applied successfully", clipId: "${clipId}", adjustments: ${JSON.stringify(adjustments)} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async applyLut(clipId: string, lutPath: string, intensity = 100): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var lutEffect = clip.addEffect("Lumetri Color"); if (!lutEffect) { JSON.stringify({ success: false, error: "Failed to add LUT effect" }); return; } // Apply LUT file try { lutEffect.properties["Input LUT"].setValue("${lutPath}"); lutEffect.properties["Input LUT Intensity"].setValue(${intensity / 100}); } catch (e) { JSON.stringify({ success: false, error: "Failed to apply LUT file: " + e.toString() }); return; } JSON.stringify({ success: true, message: "LUT applied successfully", clipId: "${clipId}", lutPath: "${lutPath}", intensity: ${intensity} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Export and Rendering Implementation private async exportSequence(sequenceId: string, outputPath: string, presetPath?: string, format?: string, quality?: string, resolution?: string): Promise<any> { try { const defaultPreset = format === 'mp4' ? 'H.264' : 'ProRes'; const preset = presetPath || defaultPreset; await this.bridge.renderSequence(sequenceId, outputPath, preset); return { success: true, message: 'Sequence exported successfully', outputPath: outputPath, format: preset, quality: quality, resolution: resolution }; } catch (error) { return { success: false, error: `Failed to export sequence: ${error instanceof Error ? error.message : String(error)}`, sequenceId: sequenceId, outputPath: outputPath }; } } private async exportFrame(sequenceId: string, time: number, outputPath: string, format = 'png'): Promise<any> { const script = ` try { var sequence = app.project.getSequenceByID("${sequenceId}"); if (!sequence) { JSON.stringify({ success: false, error: "Sequence not found" }); return; } sequence.exportFrame(new Time("${time}s"), "${outputPath}", "${format}"); JSON.stringify({ success: true, message: "Frame exported successfully", sequenceId: "${sequenceId}", time: ${time}, outputPath: "${outputPath}", format: "${format}" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } // Advanced Features Implementation private async createMulticamSequence(name: string, cameraFiles: string[], syncMethod: string): Promise<any> { const script = ` try { var multicamSource = app.project.createMulticamSource("${name}", [${cameraFiles.map(f => `"${f}"`).join(', ')}], "${syncMethod}"); if (!multicamSource) { JSON.stringify({ success: false, error: "Failed to create multicam source" }); return; } var sequence = app.project.createSequenceFromMulticamSource("${name}", multicamSource); if (!sequence) { JSON.stringify({ success: false, error: "Failed to create sequence from multicam source" }); return; } JSON.stringify({ success: true, message: "Multicam sequence created successfully", name: "${name}", sequenceId: sequence.sequenceID, cameraCount: ${cameraFiles.length}, syncMethod: "${syncMethod}" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async createProxyMedia(projectItemIds: string[], proxyPreset: string, replaceOriginals = false): Promise<any> { const script = ` try { var projectItems = [${projectItemIds.map(id => `app.project.getProjectItemByID("${id}")`).join(', ')}]; var validItems = projectItems.filter(function(item) { return item !== null; }); if (validItems.length === 0) { JSON.stringify({ success: false, error: "No valid project items found" }); return; } var proxyJob = app.encoder.createProxyJob(validItems, "${proxyPreset}"); if (!proxyJob) { JSON.stringify({ success: false, error: "Failed to create proxy job" }); return; } JSON.stringify({ success: true, message: "Proxy media creation started", proxyPreset: "${proxyPreset}", itemCount: validItems.length, replaceOriginals: ${replaceOriginals} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async autoEditToMusic(audioTrackId: string, videoClipIds: string[], editStyle: string, sensitivity = 50): Promise<any> { const script = ` try { var audioTrack = app.project.getTrackByID("${audioTrackId}"); var videoClips = [${videoClipIds.map(id => `app.project.getClipByID("${id}")`).join(', ')}]; if (!audioTrack) { JSON.stringify({ success: false, error: "Audio track not found" }); return; } var validVideoClips = videoClips.filter(function(clip) { return clip !== null; }); if (validVideoClips.length === 0) { JSON.stringify({ success: false, error: "No valid video clips found" }); return; } // This would require sophisticated beat detection and auto-editing algorithms // For now, return a placeholder response with the detected parameters JSON.stringify({ success: true, message: "Auto-edit to music analysis completed", audioTrackId: "${audioTrackId}", videoClipCount: validVideoClips.length, editStyle: "${editStyle}", sensitivity: ${sensitivity}, note: "This feature requires advanced beat detection implementation" }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async stabilizeClip(clipId: string, method = 'warp', smoothness = 50): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var stabilizationEffect = clip.addEffect("Warp Stabilizer"); if (!stabilizationEffect) { JSON.stringify({ success: false, error: "Failed to add stabilization effect" }); return; } // Configure stabilization settings try { stabilizationEffect.properties["Smoothness"].setValue(${smoothness / 100}); stabilizationEffect.properties["Method"].setValue("${method}"); } catch (e) { // Some properties might not be available } JSON.stringify({ success: true, message: "Video stabilization applied successfully", clipId: "${clipId}", method: "${method}", smoothness: ${smoothness} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } private async speedChange(clipId: string, speed: number, maintainAudio = true): Promise<any> { const script = ` try { var clip = app.project.getClipByID("${clipId}"); if (!clip) { JSON.stringify({ success: false, error: "Clip not found" }); return; } var oldSpeed = clip.speed; clip.speed = ${speed}; if (${maintainAudio} && clip.hasAudio && clip.hasAudio()) { clip.maintainAudioPitch = true; } JSON.stringify({ success: true, message: "Speed change applied successfully", clipId: "${clipId}", oldSpeed: oldSpeed, newSpeed: ${speed}, maintainAudio: ${maintainAudio} }); } catch (e) { JSON.stringify({ success: false, error: e.toString() }); } `; return await this.bridge.executeScript(script); } }

Implementation Reference

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/hetpatel-11/Adobe_Premiere_Pro_MCP'

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