- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
274 lines
12 KiB
TypeScript
Executable File
274 lines
12 KiB
TypeScript
Executable File
import { describe, expect, it } from "vitest";
|
|
import { WORKFLOW_CATALOG, WORKFLOW_PROMPTS, WORKFLOW_RESOURCE } from "../src/workflows/catalog.js";
|
|
import { annotationsForTool, structuredToolResult } from "../src/workflows/tool-metadata.js";
|
|
import { createServer } from "../src/server.js";
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import type { Telemetry, TelemetryProperties } from "../src/telemetry.js";
|
|
|
|
describe("modern MCP surface", () => {
|
|
it("publishes focused workflow prompts with inspection and verification guidance", () => {
|
|
expect(WORKFLOW_PROMPTS).toHaveLength(WORKFLOW_CATALOG.length);
|
|
expect(new Set(WORKFLOW_PROMPTS.map((prompt) => prompt.name)).size).toBe(WORKFLOW_PROMPTS.length);
|
|
|
|
const rendered = WORKFLOW_PROMPTS[0].render({ goal: "Create a 30 second teaser" });
|
|
expect(rendered.messages[0].content.text).toContain("Begin with project inspection");
|
|
expect(rendered.messages[0].content.text).toContain("inspect the result");
|
|
});
|
|
|
|
it("exposes a machine-readable workflow resource", () => {
|
|
const resource = JSON.parse(WORKFLOW_RESOURCE);
|
|
expect(resource.version).toBe(1);
|
|
expect(resource.workflows).toHaveLength(16);
|
|
expect(resource.workflows[0].recommendedTools).toContain("get_premiere_state");
|
|
const organization = resource.workflows.find((workflow: { id: string }) => workflow.id === "project-organization");
|
|
expect(organization.recommendedTools).toContain("apply_editorial_organization_plan");
|
|
expect(organization.recommendedTools).not.toContain("organize_project_items_uxp");
|
|
expect(organization.summary).toContain("advanced/manual only");
|
|
const transcriptFirst = resource.workflows.find((workflow: { id: string }) => workflow.id === "transcript-first-context");
|
|
expect(transcriptFirst.recommendedTools).toContain("create_editorial_context_pack");
|
|
});
|
|
|
|
it("marks inspection as read-only and script execution as open-world", () => {
|
|
expect(annotationsForTool("get_project_info")).toMatchObject({
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
});
|
|
expect(annotationsForTool("delete_bin")).toMatchObject({
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
});
|
|
expect(annotationsForTool("execute_extendscript").openWorldHint).toBe(true);
|
|
expect(annotationsForTool("search_project_context")).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
expect(annotationsForTool("create_context_edit_plan")).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
expect(annotationsForTool("create_editorial_context_pack")).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
expect(annotationsForTool("create_editorial_plan")).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
expect(annotationsForTool("preview_editorial_plan")).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
expect(annotationsForTool("verify_delivery_conformance")).toMatchObject({ readOnlyHint: true, idempotentHint: true, openWorldHint: false });
|
|
expect(annotationsForTool("apply_editorial_organization_plan")).toMatchObject({
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: false,
|
|
});
|
|
expect(annotationsForTool("manage_project_context")).toMatchObject({
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
});
|
|
for (const name of ["list_bins", "inspect_clip", "find_media", "check_project"]) {
|
|
expect(annotationsForTool(name)).toMatchObject({ readOnlyHint: true, idempotentHint: true });
|
|
}
|
|
for (const name of ["remove_clip", "ripple_delete_clip", "close_project"]) {
|
|
expect(annotationsForTool(name).destructiveHint).toBe(true);
|
|
}
|
|
expect(annotationsForTool("send_raw_script").openWorldHint).toBe(true);
|
|
expect(annotationsForTool("save_project")).toMatchObject({
|
|
title: "Save Project",
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
openWorldHint: false,
|
|
});
|
|
});
|
|
|
|
it("builds stable structured result envelopes", () => {
|
|
expect(structuredToolResult("ping", true, { ready: true })).toEqual({
|
|
ok: true,
|
|
tool: "ping",
|
|
data: { ready: true },
|
|
});
|
|
expect(structuredToolResult("save_project", false, undefined, "offline")).toEqual({
|
|
ok: false,
|
|
tool: "save_project",
|
|
error: "offline",
|
|
});
|
|
expect(structuredToolResult("ping", true)).toEqual({ ok: true, tool: "ping", data: null });
|
|
expect(structuredToolResult("ping", false)).toEqual({ ok: false, tool: "ping", error: "Unknown error" });
|
|
});
|
|
|
|
it("renders optional workflow constraints", () => {
|
|
const rendered = WORKFLOW_PROMPTS[1].render({ goal: "clean dialogue", constraints: "preserve room tone" });
|
|
expect(rendered.messages[0].content.text).toContain("Constraints: preserve room tone");
|
|
});
|
|
|
|
it("advertises prompts, resources, and tool annotations over MCP", async () => {
|
|
const server = createServer({ timeoutMs: 50 });
|
|
const client = new Client({ name: "modernization-test", version: "1.0.0" });
|
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
try {
|
|
const prompts = await client.listPrompts();
|
|
expect(prompts.prompts.map((prompt) => prompt.name)).toContain("premiere-rough-cut");
|
|
|
|
const resources = await client.listResources();
|
|
expect(resources.resources.map((resource) => resource.uri)).toContain("config://premiere-workflows");
|
|
expect(resources.resources.map((resource) => resource.uri)).toEqual(expect.arrayContaining([
|
|
"premiere://effects/available",
|
|
"premiere://effects/applied",
|
|
"premiere://transitions/available",
|
|
"premiere://export/presets",
|
|
"premiere://project/metadata",
|
|
]));
|
|
expect(resources.resources.map((resource) => resource.uri)).toContain("config://premiere-project-context");
|
|
expect(resources.resources.map((resource) => resource.uri)).toEqual(expect.arrayContaining([
|
|
"premiere://project/info",
|
|
"premiere://project/sequences",
|
|
"premiere://project/media",
|
|
"premiere://project/bins",
|
|
"premiere://timeline/active",
|
|
]));
|
|
|
|
const tools = await client.listTools();
|
|
expect(tools.tools.find((tool) => tool.name === "get_project_info")?.annotations?.readOnlyHint).toBe(true);
|
|
expect(tools.tools.map((tool) => tool.name)).toContain("get_capabilities");
|
|
// The default profile grants inspect/edit/export/filesystem but not
|
|
// unsafe-script, so the two scripting tools are not advertised.
|
|
expect(tools.tools.map((tool) => tool.name)).not.toContain("execute_extendscript");
|
|
expect(tools.tools.map((tool) => tool.name)).not.toContain("evaluate_expression");
|
|
expect(tools.tools).toHaveLength(347);
|
|
const capabilityTool = tools.tools.find((tool) => tool.name === "get_capabilities");
|
|
expect(capabilityTool?.outputSchema).toMatchObject({
|
|
type: "object",
|
|
// MCP clients such as Claude Desktop can require the 2020-12 dialect.
|
|
// Keep this explicit: an older SDK release silently serialized every
|
|
// registered output schema as draft-07, making every tool unusable
|
|
// before its handler could run.
|
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
required: expect.arrayContaining(["ok", "tool"]),
|
|
properties: expect.objectContaining({
|
|
ok: expect.any(Object),
|
|
tool: expect.any(Object),
|
|
data: expect.any(Object),
|
|
error: expect.any(Object),
|
|
}),
|
|
});
|
|
expect(tools.tools.map((tool) => tool.name)).toEqual(expect.arrayContaining([
|
|
"manage_project_context",
|
|
"search_project_context",
|
|
"create_context_edit_plan",
|
|
"create_editorial_context_pack",
|
|
"create_editorial_plan",
|
|
"preview_editorial_plan",
|
|
]));
|
|
expect(tools.tools.find((tool) => tool.name === "create_editorial_context_pack")?.inputSchema)
|
|
.toMatchObject({
|
|
properties: {
|
|
max_entries: { type: "integer", minimum: 1, maximum: 50 },
|
|
max_characters: { type: "integer", minimum: 1024, maximum: 24_000 },
|
|
},
|
|
});
|
|
expect(tools.tools.find((tool) => tool.name === "search_project_context")?.inputSchema)
|
|
.toMatchObject({
|
|
properties: { max_results: { type: "integer", minimum: 1, maximum: 50 } },
|
|
});
|
|
|
|
const capabilities = await client.callTool({
|
|
name: "get_capabilities",
|
|
arguments: {},
|
|
});
|
|
const capabilityData = (capabilities.structuredContent as any).data;
|
|
expect(capabilityData.tools.generatedFrom).toBe("registered-tool-catalog");
|
|
// The catalog intentionally reports every tool that exists — including
|
|
// ones this profile disables, each flagged with its authority state — so
|
|
// it is a superset of what tools/list advertises.
|
|
expect(capabilityData.tools.total).toBeGreaterThanOrEqual(tools.tools.length);
|
|
expect(capabilityData.tools.tools.map((tool: any) => tool.name)).toEqual(
|
|
expect.arrayContaining(tools.tools.map((tool) => tool.name)),
|
|
);
|
|
expect(
|
|
capabilityData.tools.tools.find((tool: any) => tool.name === "create_editorial_context_pack"),
|
|
).toMatchObject({
|
|
backend: "local",
|
|
authority: { required: "inspect", enabled: true },
|
|
verificationBoundary: "static_metadata_only",
|
|
hostVerificationRequired: false,
|
|
});
|
|
expect(
|
|
capabilityData.tools.tools.find((tool: any) => tool.name === "execute_extendscript")
|
|
?.authority,
|
|
).toMatchObject({ required: "unsafe-script", enabled: false });
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
it("narrows tools/list with an explicit workflow pack without changing the authority report", async () => {
|
|
const server = createServer({ timeoutMs: 50 }, { toolPacks: "essential" });
|
|
const client = new Client({ name: "tool-pack-test", version: "1.0.0" });
|
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
try {
|
|
const tools = await client.listTools();
|
|
const names = tools.tools.map((tool) => tool.name);
|
|
expect(names).toEqual(expect.arrayContaining([
|
|
"ping",
|
|
"get_capabilities",
|
|
"get_project_info",
|
|
"inspect_sequence_review_report",
|
|
"preview_edit_plan",
|
|
"verify_delivery_file",
|
|
]));
|
|
expect(names).not.toContain("create_bin");
|
|
// Essential is a 14-tool focused path (including the two always-visible
|
|
// diagnostics), versus 342 tools in the default full catalog.
|
|
expect(names).toHaveLength(14);
|
|
|
|
const capabilities = await client.callTool({
|
|
name: "get_capabilities",
|
|
arguments: {},
|
|
});
|
|
const capabilityData = (capabilities.structuredContent as any).data;
|
|
expect(capabilityData.toolPacks).toMatchObject({
|
|
source: "explicit",
|
|
selected: ["essential"],
|
|
fullCatalog: false,
|
|
});
|
|
expect(capabilityData.authority.enabled).toEqual(expect.arrayContaining([
|
|
"inspect",
|
|
"edit",
|
|
"export",
|
|
"filesystem",
|
|
]));
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
it("records bounded tool usage without arguments or result content", async () => {
|
|
const events: Array<{ event: string; properties?: TelemetryProperties }> = [];
|
|
const telemetry: Telemetry = {
|
|
enabled: true,
|
|
capture: (event, properties) => events.push({ event, properties }),
|
|
shutdown: async () => {},
|
|
};
|
|
const server = createServer({ timeoutMs: 50 }, { telemetry });
|
|
const client = new Client({ name: "telemetry-test", version: "1.0.0" });
|
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
try {
|
|
await client.callTool({
|
|
name: "get_capabilities",
|
|
arguments: {},
|
|
});
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0]).toMatchObject({
|
|
event: "mcp_tool_call",
|
|
properties: {
|
|
tool: "get_capabilities",
|
|
outcome: "succeeded",
|
|
},
|
|
});
|
|
expect(events[0].properties).not.toHaveProperty("arguments");
|
|
expect(events[0].properties).not.toHaveProperty("result");
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
}
|
|
});
|
|
});
|