goatedit-mcp 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/goatedit-mcp.js +87 -16
- package/package.json +1 -1
package/bundle/goatedit-mcp.js
CHANGED
|
@@ -20584,6 +20584,60 @@ var BASE_TOOLS = [
|
|
|
20584
20584
|
},
|
|
20585
20585
|
method: "setProjectSettings"
|
|
20586
20586
|
},
|
|
20587
|
+
{
|
|
20588
|
+
name: "begin_request",
|
|
20589
|
+
description: 'Call once at the start of each new request from the user, before editing. It tells the editor what kind of edit this is and which words describe the style, so it can learn what this user means by them, and measures any reference video the user gave. Pass words from the request ("punchy", "cinematic"), not the request itself \u2014 nothing else is kept. References must be in the media bin (import_local_file or add_media_file first); only their measurements are kept (cut pacing, grade, colours), never frames. Replies with those measurements, to use while editing.',
|
|
20590
|
+
inputSchema: {
|
|
20591
|
+
type: "object",
|
|
20592
|
+
properties: {
|
|
20593
|
+
editType: {
|
|
20594
|
+
type: "string",
|
|
20595
|
+
enum: ["reel", "short", "ad", "promo", "trailer", "podcast", "interview", "talking-head", "vlog", "tutorial", "explainer", "documentary", "music-video", "wedding", "event", "product", "real-estate", "gaming", "montage", "slideshow", "corporate", "other"],
|
|
20596
|
+
description: "What the user is making."
|
|
20597
|
+
},
|
|
20598
|
+
styleWords: { type: "array", items: { type: "string" }, description: 'Style words from the request, e.g. ["punchy", "warm"].' },
|
|
20599
|
+
referenceMediaIds: { type: "array", items: { type: "string" }, description: "Media ids of reference videos the user gave (up to 3)." }
|
|
20600
|
+
}
|
|
20601
|
+
},
|
|
20602
|
+
method: "beginRequest"
|
|
20603
|
+
},
|
|
20604
|
+
{
|
|
20605
|
+
name: "record_choice",
|
|
20606
|
+
description: "When you offer the user several versions of an edit (one sequence each, e.g. a fast cut, a story cut, a minimal cut) and they pick one, record it here. The editor compares the pick with the others \u2014 pacing, transitions, effects, text size, music level, grade \u2014 and learns which way this user leans, which get_style_profile then reports under preferences. A pick says more than many silent edits, so offer variants for open-ended requests.",
|
|
20607
|
+
inputSchema: {
|
|
20608
|
+
type: "object",
|
|
20609
|
+
properties: {
|
|
20610
|
+
sequenceIds: { type: "array", items: { type: "string" }, description: "Every variant offered, as sequence ids (2\u20136)." },
|
|
20611
|
+
chosenSequenceId: str("The one the user picked.")
|
|
20612
|
+
},
|
|
20613
|
+
required: ["sequenceIds", "chosenSequenceId"]
|
|
20614
|
+
},
|
|
20615
|
+
method: "recordChoice"
|
|
20616
|
+
},
|
|
20617
|
+
{
|
|
20618
|
+
name: "set_style_brand",
|
|
20619
|
+
description: "Say which brand or client this project is for, so its style is learned and read apart from the user\u2019s other clients. Ask the user first \u2014 when get_style_profile reports a brandGuess, confirm it with them before setting it. name: null files the project under the user\u2019s own style again.",
|
|
20620
|
+
inputSchema: {
|
|
20621
|
+
type: "object",
|
|
20622
|
+
properties: { name: { type: ["string", "null"], description: 'Brand or client name, e.g. "Nike"; null for none.' } },
|
|
20623
|
+
required: ["name"]
|
|
20624
|
+
},
|
|
20625
|
+
method: "setStyleBrand"
|
|
20626
|
+
},
|
|
20627
|
+
{
|
|
20628
|
+
name: "get_style_profile",
|
|
20629
|
+
description: 'What this user\u2019s edits have in common, learned from their past projects: pacing, transitions, text and captions, colour, effects, audio levels, frame shape \u2014 plus the changes they keep making to the agent\u2019s work (corrections) and what their style words ("punchy", "clean") have meant before. Read it before choosing a look, and treat it as defaults: the request in front of you wins. Pass topic to get just one part. Scoped to the project\u2019s brand/client when it has one, so one editor\u2019s clients stay apart. Reports enabled: false when the user has not switched style memory on.',
|
|
20630
|
+
inputSchema: {
|
|
20631
|
+
type: "object",
|
|
20632
|
+
properties: {
|
|
20633
|
+
topic: { type: "string", enum: ["pacing", "transitions", "text", "color", "effects", "audio", "format"], description: "One part of the style. Omit for all of it." },
|
|
20634
|
+
brandId: str("Read another brand\u2019s style instead of this project\u2019s."),
|
|
20635
|
+
editType: str("Read the style for this kind of edit (reel, ad, podcast\u2026) instead of this project\u2019s own."),
|
|
20636
|
+
sheets: bool("Also return up to 2 contact sheets of the user\u2019s own exported edits that fit (people are blurred out). Off by default: each is an image.")
|
|
20637
|
+
}
|
|
20638
|
+
},
|
|
20639
|
+
method: "getStyleProfile"
|
|
20640
|
+
},
|
|
20587
20641
|
{
|
|
20588
20642
|
name: "set_audio_master",
|
|
20589
20643
|
description: "The sequence\u2019s master bus: tag tracks as voice, music or sfx, duck music under voice, and put a safety limiter on the mix. Ducking follows the spans of clips on voice-role tracks (and nested sequences placed on them), dipping music-role tracks by duckDb, starting `attack` seconds before each line so the first word is clear. Playback, probe_audio and export all hear the same bus. With no arguments it reports the current roles and settings.",
|
|
@@ -21032,6 +21086,19 @@ function resolveRefs(value, refs) {
|
|
|
21032
21086
|
}
|
|
21033
21087
|
return value;
|
|
21034
21088
|
}
|
|
21089
|
+
function withRunProject(stepArgs, projectId) {
|
|
21090
|
+
if (typeof projectId !== "string" || !projectId || typeof stepArgs.projectId === "string") return stepArgs;
|
|
21091
|
+
return { ...stepArgs, projectId };
|
|
21092
|
+
}
|
|
21093
|
+
|
|
21094
|
+
// src/tab-registry.ts
|
|
21095
|
+
function indexTabs(reports, isOpen) {
|
|
21096
|
+
const index = /* @__PURE__ */ new Map();
|
|
21097
|
+
for (const r of [...reports].sort((a, b) => a.at - b.at)) {
|
|
21098
|
+
if (isOpen(r.socket)) index.set(r.projectId, r);
|
|
21099
|
+
}
|
|
21100
|
+
return index;
|
|
21101
|
+
}
|
|
21035
21102
|
|
|
21036
21103
|
// src/index.ts
|
|
21037
21104
|
var WEBSOCKET_PORT = 9e3;
|
|
@@ -21091,7 +21158,7 @@ var VERSION = (() => {
|
|
|
21091
21158
|
}
|
|
21092
21159
|
return "0.0.0";
|
|
21093
21160
|
})();
|
|
21094
|
-
var INSTRUCTIONS = `Edits a GoatEdit video project live in the user's browser tab at ai.goatedit.com. Call get_timeline before your first edit \u2014 clip ids change after every split, trim or delete, and a stale id is rejected. Each edit's reply carries timelineChanges (clips added, with their ids and placement; clips changed; ids removed), so take the next ids from there instead of reading the timeline again. get_timeline is lean by default; pass detail: "full" only when you need effect parameters, and trackId or from/to to read part of it. Several edits in a row go faster as one run_tools call. With more than one tab open, call list_connected_projects, then use_project to pick one. render_preview_frame and preview_media accept saveToDisk: true, which writes the frame to a file and returns its path instead of the image. If a tool reports the editor is not connected, ask the user to open ai.goatedit.com and switch on Local in the AI status menu.`;
|
|
21161
|
+
var INSTRUCTIONS = `Edits a GoatEdit video project live in the user's browser tab at ai.goatedit.com. At the start of each new request from the user, call begin_request with the kind of edit, its style words and any reference videos, then get_style_profile for how this user edits; treat that style as defaults, the request wins. Call get_timeline before your first edit \u2014 clip ids change after every split, trim or delete, and a stale id is rejected. Each edit's reply carries timelineChanges (clips added, with their ids and placement; clips changed; ids removed), so take the next ids from there instead of reading the timeline again. get_timeline is lean by default; pass detail: "full" only when you need effect parameters, and trackId or from/to to read part of it. Several edits in a row go faster as one run_tools call. With more than one tab open, call list_connected_projects, then use_project to pick one. To work on several projects at once (one helper agent per video), do not use use_project \u2014 it moves every caller \u2014 but pass projectId on each call, or once on run_tools for all its steps. render_preview_frame and preview_media accept saveToDisk: true, which writes the frame to a file and returns its path instead of the image. If a tool reports the editor is not connected, ask the user to open ai.goatedit.com and switch on Local in the AI status menu.`;
|
|
21095
21162
|
var MEDIA_TYPES = {
|
|
21096
21163
|
".mp4": { mime: "video/mp4", kind: "video" },
|
|
21097
21164
|
".m4v": { mime: "video/mp4", kind: "video" },
|
|
@@ -21347,6 +21414,14 @@ var wss = new import_websocket_server.default({
|
|
|
21347
21414
|
wss.on("error", () => {
|
|
21348
21415
|
});
|
|
21349
21416
|
var tabs = /* @__PURE__ */ new Map();
|
|
21417
|
+
var tabReports = /* @__PURE__ */ new Map();
|
|
21418
|
+
function reindexTabs() {
|
|
21419
|
+
tabs.clear();
|
|
21420
|
+
for (const [pid, r] of indexTabs(tabReports.values(), (s) => s.readyState === import_websocket.default.OPEN)) {
|
|
21421
|
+
tabs.set(pid, { ws: r.socket, projectId: pid, projectName: r.projectName });
|
|
21422
|
+
}
|
|
21423
|
+
if (lastActiveProjectId && !tabs.has(lastActiveProjectId)) lastActiveProjectId = null;
|
|
21424
|
+
}
|
|
21350
21425
|
var lastActiveProjectId = null;
|
|
21351
21426
|
var chosenProjectId = null;
|
|
21352
21427
|
var controllerSockets = /* @__PURE__ */ new Set();
|
|
@@ -21420,7 +21495,7 @@ wss.on("connection", (ws, req) => {
|
|
|
21420
21495
|
}
|
|
21421
21496
|
const target = resolveTab(projectId);
|
|
21422
21497
|
if (id !== void 0) controllerRequests.set(String(id), ws);
|
|
21423
|
-
target.ws.send(JSON.stringify({ id, method, params }));
|
|
21498
|
+
target.ws.send(JSON.stringify({ id, method, params, projectId: target.projectId }));
|
|
21424
21499
|
} catch (err) {
|
|
21425
21500
|
sendJson(ws, { id, error: err?.message ?? String(err) });
|
|
21426
21501
|
}
|
|
@@ -21433,7 +21508,6 @@ wss.on("connection", (ws, req) => {
|
|
|
21433
21508
|
return;
|
|
21434
21509
|
}
|
|
21435
21510
|
console.error("[GoatEdit MCP] Browser editor connected via WebSocket");
|
|
21436
|
-
let myProjectId = null;
|
|
21437
21511
|
ws.on("message", (data) => {
|
|
21438
21512
|
let message;
|
|
21439
21513
|
try {
|
|
@@ -21445,10 +21519,9 @@ wss.on("connection", (ws, req) => {
|
|
|
21445
21519
|
if (message.type === "status") {
|
|
21446
21520
|
if (typeof message.projectId === "string" && message.projectId) {
|
|
21447
21521
|
const pid = message.projectId;
|
|
21448
|
-
|
|
21449
|
-
myProjectId = pid;
|
|
21450
|
-
tabs.set(pid, { ws, projectId: pid, projectName: message.projectName ?? null });
|
|
21522
|
+
tabReports.set(ws, { socket: ws, projectId: pid, projectName: message.projectName ?? null, at: Date.now() });
|
|
21451
21523
|
lastActiveProjectId = pid;
|
|
21524
|
+
reindexTabs();
|
|
21452
21525
|
console.error(`[GoatEdit MCP] Active project: ${message.projectName} (${pid})`);
|
|
21453
21526
|
}
|
|
21454
21527
|
for (const controller2 of controllerSockets) sendJson(controller2, message);
|
|
@@ -21473,11 +21546,8 @@ wss.on("connection", (ws, req) => {
|
|
|
21473
21546
|
ws.on("close", () => {
|
|
21474
21547
|
console.error("[GoatEdit MCP] Browser editor disconnected");
|
|
21475
21548
|
rejectPendingOn(ws, "The editor tab disconnected");
|
|
21476
|
-
|
|
21477
|
-
|
|
21478
|
-
tabs.delete(pid);
|
|
21479
|
-
if (lastActiveProjectId === pid) lastActiveProjectId = null;
|
|
21480
|
-
}
|
|
21549
|
+
tabReports.delete(ws);
|
|
21550
|
+
reindexTabs();
|
|
21481
21551
|
});
|
|
21482
21552
|
});
|
|
21483
21553
|
var ownsPort = false;
|
|
@@ -21586,7 +21656,7 @@ async function sendToBrowser(method, params = {}, projectId) {
|
|
|
21586
21656
|
reject(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s: ${method}. Call get_timeline to see whether it landed.`));
|
|
21587
21657
|
}, REQUEST_TIMEOUT_MS);
|
|
21588
21658
|
pendingRequests.set(id, { resolve, reject, timeoutId, socket: tab.ws, method });
|
|
21589
|
-
tab.ws.send(JSON.stringify({ id, method, params }));
|
|
21659
|
+
tab.ws.send(JSON.stringify({ id, method, params, projectId: tab.projectId }));
|
|
21590
21660
|
});
|
|
21591
21661
|
}
|
|
21592
21662
|
var LOCAL_HANDLERS = {
|
|
@@ -21656,7 +21726,8 @@ var LOCAL_TOOLS = [
|
|
|
21656
21726
|
}
|
|
21657
21727
|
},
|
|
21658
21728
|
continueOnError: { type: "boolean", description: "Run the remaining steps after one fails." },
|
|
21659
|
-
rollback: { type: "boolean", description: "Take a checkpoint first and restore it if any step fails, so a run either lands whole or not at all. Cannot be combined with continueOnError." }
|
|
21729
|
+
rollback: { type: "boolean", description: "Take a checkpoint first and restore it if any step fails, so a run either lands whole or not at all. Cannot be combined with continueOnError." },
|
|
21730
|
+
projectId: { type: "string", description: "Run every step on this project (from list_connected_projects). Use it when several agents edit different tabs at once; a step's own projectId wins." }
|
|
21660
21731
|
},
|
|
21661
21732
|
required: ["calls"]
|
|
21662
21733
|
}
|
|
@@ -21743,7 +21814,7 @@ async function runTools(args) {
|
|
|
21743
21814
|
let failed = 0;
|
|
21744
21815
|
let checkpointId;
|
|
21745
21816
|
if (args.rollback) {
|
|
21746
|
-
const made = await callTool("create_checkpoint", { name: "run_tools rollback" });
|
|
21817
|
+
const made = await callTool("create_checkpoint", withRunProject({ name: "run_tools rollback" }, args.projectId));
|
|
21747
21818
|
checkpointId = made?.checkpointId;
|
|
21748
21819
|
}
|
|
21749
21820
|
for (let i = 0; i < calls.length; i++) {
|
|
@@ -21751,7 +21822,7 @@ async function runTools(args) {
|
|
|
21751
21822
|
const tool = typeof step?.tool === "string" ? step.tool : "";
|
|
21752
21823
|
try {
|
|
21753
21824
|
const stepArgs = resolveRefs(step.args && typeof step.args === "object" ? step.args : {}, refs);
|
|
21754
|
-
const raw = await callTool(tool, stepArgs);
|
|
21825
|
+
const raw = await callTool(tool, withRunProject(stepArgs, args.projectId));
|
|
21755
21826
|
if (typeof step.as === "string" && step.as) {
|
|
21756
21827
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
21757
21828
|
const { _image, _images, ...kept } = raw;
|
|
@@ -21781,7 +21852,7 @@ async function runTools(args) {
|
|
|
21781
21852
|
let rolledBack;
|
|
21782
21853
|
if (failed && checkpointId) {
|
|
21783
21854
|
try {
|
|
21784
|
-
await callTool("restore_checkpoint", { checkpointId });
|
|
21855
|
+
await callTool("restore_checkpoint", withRunProject({ checkpointId }, args.projectId));
|
|
21785
21856
|
rolledBack = true;
|
|
21786
21857
|
} catch (err) {
|
|
21787
21858
|
rolledBack = `failed: ${err?.message ?? err}. Restore ${checkpointId} with restore_checkpoint.`;
|
package/package.json
CHANGED