- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
339 lines
15 KiB
TypeScript
Executable File
339 lines
15 KiB
TypeScript
Executable File
import { describe, expect, it, vi } from "vitest";
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { createServer } from "../../src/server.js";
|
|
import { getUxpTools } from "../../src/tools/uxp.js";
|
|
import type { UxpWebSocketBridge } from "../../src/bridge/uxp-websocket-bridge.js";
|
|
|
|
type UxpTool = {
|
|
parameters: {
|
|
type?: string;
|
|
properties?: Record<string, Record<string, unknown>>;
|
|
required?: string[];
|
|
};
|
|
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
};
|
|
|
|
const ADOBE_26_3_TOOLS = [
|
|
"rename_track_uxp",
|
|
"create_subclip_uxp",
|
|
"list_markers_uxp",
|
|
"set_source_monitor_position_uxp",
|
|
"inspect_frame_alignment_uxp",
|
|
"calculate_tick_time_uxp",
|
|
"manage_sequence_preview_frame_uxp",
|
|
"inspect_source_media_provenance_uxp",
|
|
"inspect_source_proxy_uxp",
|
|
"manage_source_media_overrides_uxp",
|
|
"inspect_track_item_identity_uxp",
|
|
"slide_track_item_uxp",
|
|
"duplicate_track_item_uxp",
|
|
"ripple_delete_track_item_uxp",
|
|
"create_empty_sequence_uxp",
|
|
"audit_object_masks_uxp",
|
|
"inspect_unique_object_identity_uxp",
|
|
"inspect_effect_parameter_catalog_uxp",
|
|
"has_transcript_uxp",
|
|
"export_aaf_uxp",
|
|
] as const;
|
|
|
|
function catalog(request = vi.fn().mockResolvedValue({ outcome: "verified" })) {
|
|
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
|
|
return { request, tools: getUxpTools(bridge) as Record<string, UxpTool> };
|
|
}
|
|
|
|
describe("Adobe Premiere 26.3 UXP public MCP catalog", () => {
|
|
it("registers the sampled capability-gated tools only with a UXP bridge", async () => {
|
|
const bridge = {
|
|
request: vi.fn(),
|
|
getState: vi.fn(() => ({ status: "listening", connected: false })),
|
|
} as unknown as UxpWebSocketBridge;
|
|
const server = createServer({}, { uxpBridge: bridge });
|
|
const client = new Client({ name: "adobe-26-3-catalog-test", version: "1.0.0" });
|
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
try {
|
|
const listed = await client.listTools();
|
|
expect(listed.tools.map((tool) => tool.name)).toEqual(
|
|
expect.arrayContaining(ADOBE_26_3_TOOLS),
|
|
);
|
|
expect(listed.tools).toHaveLength(440);
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
it("publishes bounded schemas for Adobe's documented 26.3 APIs", () => {
|
|
const { tools } = catalog();
|
|
expect(Object.keys(tools)).toEqual(expect.arrayContaining(ADOBE_26_3_TOOLS));
|
|
|
|
expect(tools.rename_track_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
required: ["track_type", "track_index", "name"],
|
|
properties: {
|
|
track_type: { type: "string", enum: ["audio", "video", "caption"] },
|
|
track_index: { type: "integer" },
|
|
name: { type: "string" },
|
|
operation_id: { type: "string" },
|
|
},
|
|
});
|
|
expect(tools.create_subclip_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
required: ["name", "start_seconds", "end_seconds"],
|
|
properties: {
|
|
project_item_id: { type: "string" },
|
|
project_item_name: { type: "string" },
|
|
name: { type: "string" },
|
|
start_seconds: { type: "number" },
|
|
end_seconds: { type: "number" },
|
|
hard_boundaries: { type: "boolean" },
|
|
take_video: { type: "boolean" },
|
|
take_audio: { type: "boolean" },
|
|
operation_id: { type: "string" },
|
|
},
|
|
});
|
|
expect(tools.list_markers_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
properties: {
|
|
scope: { type: "string", enum: ["sequence", "project_item"] },
|
|
project_item_id: { type: "string" },
|
|
project_item_name: { type: "string" },
|
|
filters: { type: "array", items: { type: "string" } },
|
|
include_web_links: { type: "boolean" },
|
|
include_color_values: { type: "boolean" },
|
|
},
|
|
});
|
|
expect(tools.set_source_monitor_position_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
required: ["seconds"],
|
|
properties: { seconds: { type: "number" }, operation_id: { type: "string" } },
|
|
});
|
|
expect(tools.inspect_frame_alignment_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false, required: ["action", "frame_rate"],
|
|
properties: {
|
|
action: { type: "string", enum: ["align", "frame"] },
|
|
frame_rate: { type: "number", minimum: 1, maximum: 240 },
|
|
seconds: { type: "number", minimum: 0, maximum: 86400 },
|
|
frame_count: { type: "integer", minimum: 0, maximum: 20736000 },
|
|
},
|
|
});
|
|
expect(tools.calculate_tick_time_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false, required: ["operation", "base_ticks"],
|
|
properties: {
|
|
operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] },
|
|
base_ticks: { type: "string", pattern: "^(?:0|-?[1-9][0-9]{0,17})$" },
|
|
operand_ticks: { type: "string", pattern: "^(?:0|-?[1-9][0-9]{0,17})$" },
|
|
factor: { type: "integer", minimum: -1_000_000, maximum: 1_000_000 },
|
|
},
|
|
});
|
|
expect(tools.manage_sequence_preview_frame_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false, required: ["action", "sequence_id"],
|
|
properties: {
|
|
action: { type: "string", enum: ["inspect", "update"] },
|
|
sequence_id: { type: "string", minLength: 1, maxLength: 128 },
|
|
preview_width: { type: "integer", minimum: 16, maximum: 10240 },
|
|
preview_height: { type: "integer", minimum: 16, maximum: 8192 },
|
|
expected_snapshot: { type: "object", additionalProperties: false, required: ["project_guid", "sequence_id", "preview_width", "preview_height"] },
|
|
confirm_set_preview_frame: { type: "boolean" }, operation_id: { type: "string" },
|
|
},
|
|
});
|
|
expect(tools.inspect_source_media_provenance_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false, required: ["project_item_id"],
|
|
properties: {
|
|
project_item_id: { type: "string", minLength: 1, maxLength: 512 },
|
|
include_media_file_path: { type: "boolean" },
|
|
include_originating_project_path: { type: "boolean" },
|
|
},
|
|
});
|
|
expect(tools.inspect_source_proxy_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false, required: ["project_item_id"],
|
|
properties: {
|
|
project_item_id: { type: "string", minLength: 1, maxLength: 512 },
|
|
include_proxy_path: { type: "boolean" },
|
|
},
|
|
});
|
|
expect(tools.create_empty_sequence_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["name", "confirm_non_undoable", "operation_id"],
|
|
properties: {
|
|
name: { type: "string" },
|
|
confirm_non_undoable: { type: "boolean" },
|
|
operation_id: { type: "string" },
|
|
},
|
|
});
|
|
expect(tools.audit_object_masks_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
additionalProperties: false,
|
|
properties: {
|
|
expected_project_guid: { type: "string", minLength: 1, maxLength: 512 },
|
|
sequence_ids: {
|
|
type: "array", minItems: 1, maxItems: 64, uniqueItems: true,
|
|
items: { type: "string", minLength: 1, maxLength: 512 },
|
|
},
|
|
},
|
|
});
|
|
expect(tools.inspect_unique_object_identity_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false,
|
|
properties: {
|
|
project_item_id: { type: "string", minLength: 1, maxLength: 512 },
|
|
sequence_guid: { type: "string", minLength: 1, maxLength: 512 },
|
|
expected_project_guid: { type: "string", minLength: 1, maxLength: 512 },
|
|
expected_unique_id: { type: "string", minLength: 1, maxLength: 512 },
|
|
},
|
|
oneOf: [{ required: ["project_item_id"] }, { required: ["sequence_guid"] }],
|
|
});
|
|
expect(tools.inspect_track_item_identity_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["media_type", "track_index", "clip_index"],
|
|
properties: {
|
|
media_type: { type: "string", enum: ["video", "audio"] },
|
|
track_index: { type: "integer", minimum: 0 },
|
|
clip_index: { type: "integer", minimum: 0 },
|
|
expected_sequence_guid: { type: "string", maxLength: 512 },
|
|
},
|
|
});
|
|
expect(tools.inspect_effect_parameter_catalog_uxp.parameters).toMatchObject({
|
|
type: "object", additionalProperties: false,
|
|
required: ["media_type", "track_index", "clip_index", "component_index"],
|
|
properties: {
|
|
media_type: { enum: ["video", "audio"] },
|
|
track_index: { maximum: 511 }, clip_index: { maximum: 511 }, component_index: { maximum: 511 },
|
|
expected_sequence_guid: { maxLength: 512 }, expected_component_id: { maxLength: 512 },
|
|
},
|
|
});
|
|
expect(tools.has_transcript_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
properties: {
|
|
project_item_id: { type: "string" },
|
|
project_item_name: { type: "string" },
|
|
},
|
|
});
|
|
expect(tools.export_aaf_uxp.parameters).toMatchObject({
|
|
type: "object",
|
|
required: ["output_file_path"],
|
|
properties: {
|
|
output_file_path: { type: "string" },
|
|
operation_id: { type: "string" },
|
|
options: {
|
|
type: "object",
|
|
properties: {
|
|
mixdown_video: { type: "boolean" },
|
|
explode_to_mono: { type: "boolean" },
|
|
embed_audio: { type: "boolean" },
|
|
trim_sources: { type: "boolean" },
|
|
render_audio_effects: { type: "boolean" },
|
|
interleave_without_effects: { type: "boolean" },
|
|
preserve_parent_folder: { type: "boolean" },
|
|
sample_rate: { type: "integer", enum: [32000, 44100, 48000, 88200, 96000] },
|
|
bits_per_sample: { type: "integer", enum: [16, 24, 32] },
|
|
audio_file_format: { type: "string", enum: ["aiff", "wav"] },
|
|
handle_frames: { type: "integer" },
|
|
video_mixdown_preset_path: { type: "string" },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("translates the public snake_case arguments to protocol arguments", async () => {
|
|
const request = vi.fn().mockResolvedValue({ outcome: "verified" });
|
|
const { tools } = catalog(request);
|
|
|
|
await tools.rename_track_uxp.handler({
|
|
track_type: "caption", track_index: 2, name: "Dialogue", operation_id: "track-1",
|
|
});
|
|
await tools.create_subclip_uxp.handler({
|
|
project_item_id: "clip-17", name: "Sentence", start_seconds: 1.25, end_seconds: 4.75,
|
|
hard_boundaries: true, take_video: false, take_audio: true, operation_id: "subclip-1",
|
|
});
|
|
await tools.list_markers_uxp.handler({
|
|
scope: "project_item", project_item_id: "clip-17", filters: ["Comment", "Chapter"], include_web_links: true, include_color_values: true,
|
|
});
|
|
await tools.set_source_monitor_position_uxp.handler({ seconds: 12.5, operation_id: "source-1" });
|
|
await tools.inspect_frame_alignment_uxp.handler({ action: "align", frame_rate: 24, seconds: 1.03 });
|
|
await tools.calculate_tick_time_uxp.handler({ operation: "add", base_ticks: "1", operand_ticks: "2" });
|
|
await tools.manage_sequence_preview_frame_uxp.handler({
|
|
action: "update", sequence_id: "sequence-1", preview_width: 1920, preview_height: 1080,
|
|
expected_snapshot: { project_guid: "project-1", sequence_id: "sequence-1", preview_width: 640, preview_height: 360 },
|
|
confirm_set_preview_frame: true, operation_id: "preview-frame-1",
|
|
});
|
|
await tools.inspect_source_media_provenance_uxp.handler({
|
|
project_item_id: "clip-17", include_media_file_path: true, include_originating_project_path: false,
|
|
});
|
|
await tools.inspect_source_proxy_uxp.handler({ project_item_id: "clip-17", include_proxy_path: true });
|
|
await tools.create_empty_sequence_uxp.handler({
|
|
name: "Empty Assembly", confirm_non_undoable: true, operation_id: "empty-1",
|
|
});
|
|
await tools.audit_object_masks_uxp.handler({
|
|
expected_project_guid: "project-1", sequence_ids: ["sequence-2", "sequence-1"],
|
|
});
|
|
await tools.inspect_track_item_identity_uxp.handler({
|
|
media_type: "video", track_index: 1, clip_index: 2, expected_sequence_guid: "sequence-1",
|
|
});
|
|
await tools.has_transcript_uxp.handler({ project_item_name: "Interview A" });
|
|
await tools.export_aaf_uxp.handler({
|
|
output_file_path: "/exports/turnover.aaf",
|
|
options: {
|
|
mixdown_video: true, explode_to_mono: true, embed_audio: false, trim_sources: true,
|
|
render_audio_effects: true, interleave_without_effects: false, preserve_parent_folder: true,
|
|
sample_rate: 48000, bits_per_sample: 24, audio_file_format: "wav", handle_frames: 12,
|
|
video_mixdown_preset_path: "/presets/prores.epr",
|
|
},
|
|
operation_id: "aaf-1",
|
|
});
|
|
|
|
expect(request).toHaveBeenNthCalledWith(1, "track.rename", {
|
|
trackType: "caption", trackIndex: 2, name: "Dialogue", operationId: "track-1",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(2, "subclip.create", {
|
|
projectItemId: "clip-17", name: "Sentence", startSeconds: 1.25, endSeconds: 4.75,
|
|
hasHardBoundaries: true, takeVideo: false, takeAudio: true, operationId: "subclip-1",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(3, "marker.list", {
|
|
scope: "projectItem", projectItemId: "clip-17", filters: ["Comment", "Chapter"], includeWebLinks: true, includeColorValues: true,
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(4, "sourceMonitor.position.set", {
|
|
seconds: 12.5, operationId: "source-1",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(5, "time.frameAlignment.inspect", {
|
|
action: "align", frameRate: 24, seconds: 1.03,
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(6, "time.tickArithmetic.inspect", {
|
|
operation: "add", baseTicks: "1", operandTicks: "2",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(7, "sequence.previewFrame.update", {
|
|
sequenceId: "sequence-1", previewWidth: 1920, previewHeight: 1080, confirmSetPreviewFrame: true, operationId: "preview-frame-1",
|
|
expectedSnapshot: { projectGuid: "project-1", sequenceId: "sequence-1", previewWidth: 640, previewHeight: 360 },
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(8, "source.provenance.inspect", {
|
|
projectItemId: "clip-17", includeMediaFilePath: true, includeOriginatingProjectPath: false,
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(9, "source.proxy.inspect", {
|
|
projectItemId: "clip-17", includeProxyPath: true,
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(10, "sequences.createEmpty", {
|
|
name: "Empty Assembly", confirmNonUndoable: true, operationId: "empty-1",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(11, "objectMask.audit", {
|
|
expectedProjectGuid: "project-1", sequenceIds: ["sequence-2", "sequence-1"],
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(12, "trackItem.identity.inspect", {
|
|
mediaType: "video", trackIndex: 1, clipIndex: 2, expectedSequenceGuid: "sequence-1",
|
|
});
|
|
expect(request).toHaveBeenNthCalledWith(13, "transcript.has", { projectItemName: "Interview A" });
|
|
expect(request).toHaveBeenNthCalledWith(14, "interchange.aaf.export", {
|
|
outputFilePath: "/exports/turnover.aaf",
|
|
options: {
|
|
mixdownVideo: true, explodeToMono: true, embedAudio: false, trimSources: true,
|
|
renderAudioEffects: true, interleaveWithoutEffects: false, preserveParentFolder: true,
|
|
sampleRate: 48000, bitsPerSample: 24, audioFileFormat: "wav", handleFrames: 12,
|
|
videoMixdownPresetPath: "/presets/prores.epr",
|
|
},
|
|
operationId: "aaf-1",
|
|
});
|
|
});
|
|
});
|