Skip to main content
Glama

Premiere Pro MCP Server

by jordanl61
mcp-server.js30.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import fetch from 'node-fetch'; class PremiereProMCPServer { constructor() { this.server = new Server( { name: 'premiere-pro-mcp', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); this.setupErrorHandling(); } setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools = [ { name: "get_project_info", description: "Get basic information about the current Premiere Pro project", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_active_sequence_info", description: "Get detailed information about the currently active sequence", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "list_all_sequences", description: "List all sequences in the current project with basic info", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_sequence_details", description: "Get detailed information about a specific sequence including tracks, effects, and markers", inputSchema: { type: "object", properties: { sequence_name: { type: "string", description: "Name of the sequence to get details for" } }, required: ["sequence_name"] } }, { name: "get_timeline_structure", description: "Get the track structure of the active sequence", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_timeline_clips", description: "Get all clips in the active sequence with detailed information", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_project_media", description: "Get all media items in the project browser with file information", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_project_bins", description: "Get project bin structure and organization", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_playhead_info", description: "Get current playhead position and playback state", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_selection_info", description: "Get information about currently selected clips or time range", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_export_presets", description: "Get available export presets and their settings", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_render_queue", description: "Get current render queue status and items", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "trim_clip_by_frames", description: "Trim or extend the in/out point of a video or audio clip by a number of frames.", inputSchema: { type: "object", properties: { sequenceId: { type: "number", description: "Index of the sequence (0-based)" }, clipId: { type: "string", description: "ID of the clip to trim" }, framesDelta: { type: "number", description: "Number of frames to trim (positive or negative)" }, direction: { type: "string", enum: ["in", "out"], description: "Which edit point to trim ('in' or 'out')" }, trackType: { type: "string", enum: ["video", "audio"], description: "Track type ('video' or 'audio')" } }, required: ["sequenceId", "clipId", "framesDelta", "direction", "trackType"] } }, { name: 'create_sequence', description: 'Create a new sequence in Premiere Pro', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the new sequence', }, width: { type: 'number', description: 'Width in pixels (default: 1920)', }, height: { type: 'number', description: 'Height in pixels (default: 1080)', }, framerate: { type: 'number', description: 'Frame rate (default: 23.976)', }, }, required: ['name'], }, }, { name: 'export_project', description: 'Export the current project or sequence', inputSchema: { type: 'object', properties: { output_path: { type: 'string', description: 'Output file path', }, preset_name: { type: 'string', description: 'Export preset name (default: H.264 High Quality)', }, include_audio: { type: 'boolean', description: 'Include audio in export (default: true)', }, }, required: ['output_path'], }, }, ]; return { tools, }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_project_info': return await this.getProjectInfo(); case 'get_active_sequence_info': return await this.getActiveSequenceInfo(); case 'list_all_sequences': return await this.listAllSequences(); case 'get_sequence_details': return await this.getSequenceDetails(args.sequence_name); case 'get_timeline_structure': return await this.getTimelineStructure(); case 'get_timeline_clips': return await this.getTimelineClips(); case 'get_project_media': return await this.getProjectMedia(); case 'get_project_bins': return await this.getProjectBins(); case 'get_playhead_info': return await this.getPlayheadInfo(); case 'get_selection_info': return await this.getSelectionInfo(); case 'get_export_presets': return await this.getExportPresets(); case 'get_render_queue': return await this.getRenderQueue(); case 'create_sequence': return await this.createSequence(args); case 'export_project': return await this.exportProject(args); case 'trim_clip_by_frames': { // Dynamically import and call the ExtendScript function const { sequenceId, clipId, framesDelta, direction, trackType } = args; // Here you would use your existing bridge to call the ExtendScript file // For demonstration, we'll assume a function runExtendScriptFile exists const { runExtendScriptFile } = require('./runExtendScriptFile'); const result = await runExtendScriptFile('trimClipByFrames.jsx', 'trimClipByFrames', [sequenceId, clipId, framesDelta, direction, trackType]); if (result && result.success) { return { content: [ { type: 'text', text: `✅ Clip trimmed successfully.` } ] }; } else { return { content: [ { type: 'text', text: `❌ Failed to trim clip: ${result && result.error ? result.error : 'Unknown error'}` } ], isError: true }; } } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `\u274c Error executing ${name}: ${error.message}`, }, ], isError: true, }; } }); } async getProjectInfo() { try { // Fetch real project info from HTTP server which communicates with Premiere Pro const response = await fetch('http://localhost:3001/api/project-stats'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const projectData = await response.json(); return { content: [ { type: 'text', text: `**Current Premiere Pro Project Information:** 📽️ **Project**: ${projectData.projectName || 'Unknown Project'} 🎬 **Sequences**: ${projectData.sequences || 0} 🎥 **Clips**: ${projectData.clips || 0} 📁 **Bins**: ${projectData.bins || 0} 🛤️ **Tracks**: ${projectData.tracks || 0} ⏱️ **Duration**: ${projectData.duration || 'Unknown'} *Retrieved from active Premiere Pro instance*`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Error getting project info**: ${error.message} **Troubleshooting:** - Ensure Premiere Pro is running - Check that HTTP server is running on port 3001 - Verify the MCP extension is loaded in Premiere Pro - Make sure a project is open in Premiere Pro`, }, ], isError: true, }; } } async getActiveSequenceInfo() { try { const response = await fetch('http://localhost:3001/api/active-sequence'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}\n\n🔧 **Troubleshooting Steps:**\n1. Make sure Premiere Pro is running\n2. Open a project with an active sequence\n3. Ensure the CEP extension is loaded\n4. Click "Refresh Project Info" in the extension`, }, ], }; } return { content: [ { type: 'text', text: `🎬 **Active Sequence Details**\n\n**Name:** ${data.sequence_name}\n**Duration:** ${data.duration}\n**Frame Rate:** ${data.frame_rate} fps\n**Resolution:** ${data.resolution.width}x${data.resolution.height}\n**Audio Sample Rate:** ${data.audio_sample_rate} Hz\n**Timecode Start:** ${data.timecode_start}\n**Playhead Position:** ${data.playhead_position}\n**Video Tracks:** ${data.track_count.video_tracks}\n**Audio Tracks:** ${data.track_count.audio_tracks}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get active sequence info**\n\nError: ${error.message}\n\n🔧 **Troubleshooting:**\n1. Check that the HTTP server is running on port 3001\n2. Ensure Premiere Pro is open with an active sequence\n3. Verify CEP extension is loaded and functional`, }, ], isError: true, }; } } async listAllSequences() { try { const response = await fetch('http://localhost:3001/api/sequences'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const sequenceList = data.sequences.map(seq => `• **${seq.name}** (${seq.duration}) - ${seq.resolution} @ ${seq.frame_rate}fps - ${seq.clip_count} clips ${seq.is_active ? '✅ ACTIVE' : ''}` ).join('\n'); return { content: [ { type: 'text', text: `🎬 **All Sequences (${data.total_sequences})**\n\n${sequenceList}\n\n**Active Sequence:** ${data.active_sequence}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to list sequences**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getSequenceDetails(sequenceName) { try { const response = await fetch(`http://localhost:3001/api/sequence-details?name=${encodeURIComponent(sequenceName)}`); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const videoTracks = data.tracks.video_tracks.map(track => ` • Track ${track.track_number}: ${track.track_name} (${track.clip_count} clips) ${track.is_locked ? '🔒' : ''} ${track.is_visible ? '👁️' : '🙈'}` ).join('\n'); const audioTracks = data.tracks.audio_tracks.map(track => ` • Track ${track.track_number}: ${track.track_name} (${track.clip_count} clips) ${track.is_locked ? '🔒' : ''} ${track.is_muted ? '🔇' : '🔊'} ${track.is_solo ? '🎯' : ''}` ).join('\n'); return { content: [ { type: 'text', text: `🎬 **Sequence Details: ${data.sequence_name}**\n\n**Settings:**\n• Resolution: ${data.settings.resolution.width}x${data.settings.resolution.height}\n• Frame Rate: ${data.settings.frame_rate} fps\n• Audio: ${data.settings.audio_sample_rate} Hz\n\n**Video Tracks:**\n${videoTracks}\n\n**Audio Tracks:**\n${audioTracks}\n\n**Applied Effects:** ${data.effects_applied.join(', ')}\n**Markers:** ${data.markers.length}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get sequence details**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getTimelineStructure() { try { const response = await fetch('http://localhost:3001/api/timeline-structure'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const videoTracks = data.video_tracks.map(track => ` • V${track.track_index}: ${track.track_name} ${track.is_locked ? '🔒' : ''} ${track.is_visible ? '👁️' : '🙈'} (${track.blend_mode})` ).join('\n'); const audioTracks = data.audio_tracks.map(track => ` • A${track.track_index}: ${track.track_name} ${track.is_locked ? '🔒' : ''} ${track.is_muted ? '🔇' : '🔊'} ${track.is_solo ? '🎯' : ''} (Vol: ${track.volume}dB, Pan: ${track.pan})` ).join('\n'); return { content: [ { type: 'text', text: `🎬 **Timeline Structure: ${data.sequence_name}**\n\n**Video Tracks:**\n${videoTracks}\n\n**Audio Tracks:**\n${audioTracks}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get timeline structure**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getTimelineClips() { try { const response = await fetch('http://localhost:3001/api/timeline-clips'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const clipsList = data.clips.slice(0, 20).map(clip => `• **${clip.clip_name}** (${clip.track_type}${clip.track_number})\n 📍 ${clip.timeline_in} → ${clip.timeline_out} (${clip.duration})\n 📁 ${clip.source_file_path.split('\\').pop()}\n ⚡ ${clip.speed}% ${clip.effects.length > 0 ? `| Effects: ${clip.effects.join(', ')}` : ''}` ).join('\n\n'); return { content: [ { type: 'text', text: `🎬 **Timeline Clips (${data.total_clips} total${data.total_clips > 20 ? ', showing first 20' : ''})**\n\n${clipsList}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get timeline clips**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getProjectMedia() { try { const response = await fetch('http://localhost:3001/api/project-media'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const mediaList = data.media_items.slice(0, 15).map(media => `• **${media.file_name}** (${media.duration})\n 📐 ${media.resolution.width}x${media.resolution.height} @ ${media.frame_rate}fps\n 💾 ${media.file_size_mb}MB | 🎵 ${media.audio_channels}ch @ ${media.audio_sample_rate}Hz\n 📁 ${media.bin_location} | Used: ${media.usage_count}x ${media.is_offline ? '❌ OFFLINE' : '✅'}` ).join('\n\n'); return { content: [ { type: 'text', text: `📁 **Project Media (${data.total_media_count} items${data.total_media_count > 15 ? ', showing first 15' : ''})**\n\n${mediaList}\n\n**Total Duration:** ${data.total_duration}\n**Offline Media:** ${data.offline_media_count} items`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get project media**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getProjectBins() { try { const response = await fetch('http://localhost:3001/api/project-bins'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const binsList = data.bins.map(bin => { const indent = bin.parent_bin ? ' ' : ''; const subBins = bin.sub_bins.length > 0 ? ` (${bin.sub_bins.length} sub-bins)` : ''; return `${indent}📁 **${bin.bin_name}** - ${bin.media_count} items${subBins} ${bin.color_label ? `🏷️ ${bin.color_label}` : ''}`; }).join('\n'); return { content: [ { type: 'text', text: `📁 **Project Bins (${data.total_bins} total)**\n\n${binsList}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get project bins**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getPlayheadInfo() { try { const response = await fetch('http://localhost:3001/api/playhead'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } return { content: [ { type: 'text', text: `⏱️ **Playhead Info**\n\n**Sequence:** ${data.sequence_name}\n**Timecode:** ${data.timecode}\n**Frame:** ${data.frame_number}\n**Progress:** ${data.percentage_complete}%\n**Status:** ${data.is_playing ? '▶️ Playing' : '⏸️ Paused'}\n**Speed:** ${data.playback_speed}x`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get playhead info**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getSelectionInfo() { try { const response = await fetch('http://localhost:3001/api/selection'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } if (data.selection_type === 'none') { return { content: [ { type: 'text', text: `🎯 **Selection Info**\n\nNo clips or time range currently selected.`, }, ], }; } const selectedClips = data.selected_clips.map(clip => `• **${clip.clip_name}** (${clip.track_type}${clip.track_number})` ).join('\n'); return { content: [ { type: 'text', text: `🎯 **Selection Info**\n\n**Type:** ${data.selection_type}\n**Selected Clips:**\n${selectedClips}\n\n**Time Range:** ${data.selection_in} → ${data.selection_out}\n**Duration:** ${data.selection_duration}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get selection info**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getExportPresets() { try { const response = await fetch('http://localhost:3001/api/export-presets'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } const presetsList = data.presets.map(preset => `• **${preset.preset_name}** (${preset.format})\n 📐 ${preset.resolution.width}x${preset.resolution.height} @ ${preset.frame_rate}fps\n 📊 Video: ${preset.bitrate} | Audio: ${preset.audio_codec} @ ${preset.audio_bitrate}` ).join('\n\n'); return { content: [ { type: 'text', text: `🎥 **Export Presets**\n\n${presetsList}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get export presets**\n\nError: ${error.message}`, }, ], isError: true, }; } } async getRenderQueue() { try { const response = await fetch('http://localhost:3001/api/render-queue'); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } if (data.total_queue_items === 0) { return { content: [ { type: 'text', text: `🎬 **Render Queue**\n\nNo items in render queue.`, }, ], }; } const queueList = data.queue_items.map(item => { const statusEmoji = { 'queued': '⏳', 'rendering': '🔄', 'complete': '✅', 'error': '❌' }[item.status] || '❓'; return `${statusEmoji} **${item.sequence_name}**\n 📁 ${item.output_path}\n ⚙️ ${item.preset} | Progress: ${item.progress_percentage}%\n ⏱️ ETA: ${item.estimated_time_remaining}`; }).join('\n\n'); return { content: [ { type: 'text', text: `🎬 **Render Queue (${data.total_queue_items} items)**\n\n${queueList}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to get render queue**\n\nError: ${error.message}`, }, ], isError: true, }; } } async createSequence(args) { const { name, width = 1920, height = 1080, framerate = 23.976 } = args; try { const response = await fetch('http://localhost:3001/api/create-sequence', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, width, height, framerate }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } return { content: [ { type: 'text', text: `✅ **Sequence Created Successfully**\n\n**Name:** ${data.sequence_name}\n**Resolution:** ${data.resolution.width}x${data.resolution.height}\n**Frame Rate:** ${data.frame_rate} fps\n**Created:** ${data.created_timestamp}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to create sequence**\n\nError: ${error.message}`, }, ], isError: true, }; } } async exportProject(args) { const { output_path, preset_name = "H.264 High Quality", include_audio = true } = args; try { const response = await fetch('http://localhost:3001/api/export-project', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ output_path, preset_name, include_audio }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const data = await response.json(); if (data.error) { return { content: [ { type: 'text', text: `⚠️ ${data.error}`, }, ], }; } if (data.status === 'queued') { return { content: [ { type: 'text', text: `🎬 **Export Queued Successfully**\n\n**Output Path:** ${data.output_path}\n**Preset:** ${data.preset_name}\n**Sequence:** ${data.sequence_name}\n**Queue Position:** ${data.queue_position}\n**Estimated Duration:** ${data.estimated_duration}`, }, ], }; } else { return { content: [ { type: 'text', text: `✅ **Export Started Successfully**\n\n**Output Path:** ${data.output_path}\n**Preset:** ${data.preset_name}\n**Sequence:** ${data.sequence_name}\n**Status:** ${data.status}`, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: `❌ **Failed to export project**\n\nError: ${error.message}`, }, ], isError: true, }; } } setupErrorHandling() { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Premiere Pro MCP server running on stdio'); } } const server = new PremiereProMCPServer(); server.run().catch(console.error);

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/jordanl61/premiere-pro-mcp-server'

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