@hadialmarzooq/agent-media-mcp 0.2.0 → 0.3.0
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/dist/index.js +378 -80
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -11,12 +11,18 @@ import {
|
|
|
11
11
|
parsePlan,
|
|
12
12
|
planMedia,
|
|
13
13
|
validatePlan,
|
|
14
|
-
verifyMedia
|
|
14
|
+
verifyMedia,
|
|
15
|
+
inspectPlanIssues,
|
|
16
|
+
repairPlan,
|
|
17
|
+
parseReceipt,
|
|
18
|
+
mediaPlanSchemaId,
|
|
19
|
+
mediaPlanSchemaVersion
|
|
15
20
|
} from "@hadialmarzooq/agent-media-core";
|
|
16
21
|
import {
|
|
17
22
|
concatenate,
|
|
18
23
|
executePlan,
|
|
19
24
|
extractAudio,
|
|
25
|
+
resumeFromReceipt,
|
|
20
26
|
extractFrame,
|
|
21
27
|
getCapabilities,
|
|
22
28
|
inspectMedia,
|
|
@@ -54,7 +60,82 @@ function validateGoalSchema(goals) {
|
|
|
54
60
|
}
|
|
55
61
|
return goals;
|
|
56
62
|
}
|
|
57
|
-
var planRefSchema = z.object({ irVersion: z.
|
|
63
|
+
var planRefSchema = z.object({ irVersion: z.string().min(1), source: z.object({ path: z.string() }) }).passthrough();
|
|
64
|
+
var videoStreamSchema = z.object({
|
|
65
|
+
width: z.number(),
|
|
66
|
+
height: z.number(),
|
|
67
|
+
aspectRatio: z.string(),
|
|
68
|
+
fps: z.number().optional(),
|
|
69
|
+
codec: z.string().optional(),
|
|
70
|
+
pixelFormat: z.string().optional(),
|
|
71
|
+
rotationDegrees: z.number().optional()
|
|
72
|
+
});
|
|
73
|
+
var mediaMetadataShape = {
|
|
74
|
+
path: z.string(),
|
|
75
|
+
kind: z.enum(["video", "audio", "image", "unknown"]),
|
|
76
|
+
durationSeconds: z.number().optional(),
|
|
77
|
+
container: z.string().optional(),
|
|
78
|
+
sizeBytes: z.number(),
|
|
79
|
+
video: videoStreamSchema.optional(),
|
|
80
|
+
audio: z.object({
|
|
81
|
+
present: z.boolean(),
|
|
82
|
+
codec: z.string().optional(),
|
|
83
|
+
sampleRate: z.number().optional(),
|
|
84
|
+
channels: z.number().optional()
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
var mediaMetadataSchema = z.object(mediaMetadataShape);
|
|
88
|
+
var verificationShape = {
|
|
89
|
+
passed: z.boolean(),
|
|
90
|
+
checks: z.record(
|
|
91
|
+
z.string(),
|
|
92
|
+
z.object({
|
|
93
|
+
passed: z.boolean(),
|
|
94
|
+
expected: z.unknown(),
|
|
95
|
+
actual: z.unknown(),
|
|
96
|
+
message: z.string()
|
|
97
|
+
})
|
|
98
|
+
),
|
|
99
|
+
failures: z.array(z.string()),
|
|
100
|
+
warnings: z.array(z.string())
|
|
101
|
+
};
|
|
102
|
+
var verificationSchema = z.object(verificationShape);
|
|
103
|
+
var planSchema = z.object({
|
|
104
|
+
irVersion: z.literal("1"),
|
|
105
|
+
source: z.object({ path: z.string() }),
|
|
106
|
+
constraints: z.record(z.string(), z.unknown()),
|
|
107
|
+
steps: z.array(z.record(z.string(), z.unknown())),
|
|
108
|
+
expectations: z.record(z.string(), z.unknown())
|
|
109
|
+
});
|
|
110
|
+
var echoedObject = z.looseObject({});
|
|
111
|
+
var receiptSchema = z.looseObject({ receiptVersion: z.string(), planFingerprint: z.string() });
|
|
112
|
+
var workflowResultShape = {
|
|
113
|
+
source: echoedObject,
|
|
114
|
+
plan: echoedObject,
|
|
115
|
+
output: mediaMetadataSchema,
|
|
116
|
+
verification: verificationSchema,
|
|
117
|
+
resumed: z.boolean().optional(),
|
|
118
|
+
receipt: receiptSchema.optional()
|
|
119
|
+
};
|
|
120
|
+
var contentChecksSchema = z.object({
|
|
121
|
+
blackFrames: z.union([z.boolean(), z.object({ minDurationSeconds: z.number().positive() })]).optional(),
|
|
122
|
+
silence: z.union([
|
|
123
|
+
z.boolean(),
|
|
124
|
+
z.object({
|
|
125
|
+
thresholdDb: z.number().optional(),
|
|
126
|
+
minDurationSeconds: z.number().positive().optional()
|
|
127
|
+
})
|
|
128
|
+
]).optional(),
|
|
129
|
+
freeze: z.union([z.boolean(), z.object({ minDurationSeconds: z.number().positive() })]).optional(),
|
|
130
|
+
completeness: z.boolean().optional()
|
|
131
|
+
}).strict();
|
|
132
|
+
var contentCheckInputs = {
|
|
133
|
+
contentChecks: contentChecksSchema.optional().describe("Decode the output once and check what it contains, not just its shape."),
|
|
134
|
+
warnOnly: z.array(z.string().min(1)).optional().describe("Check names that warn instead of failing the call.")
|
|
135
|
+
};
|
|
136
|
+
function contentCheckOptions(value) {
|
|
137
|
+
return value === void 0 ? {} : { contentChecks: value };
|
|
138
|
+
}
|
|
58
139
|
var readOnlyHint = { readOnlyHint: true };
|
|
59
140
|
var destructiveHint = { destructiveHint: true };
|
|
60
141
|
function createMcpServer() {
|
|
@@ -64,6 +145,7 @@ function createMcpServer() {
|
|
|
64
145
|
{
|
|
65
146
|
description: "Inspect normalized media metadata. Paths resolve against the server working directory; absolute paths are recommended.",
|
|
66
147
|
inputSchema: { input: z.string().min(1) },
|
|
148
|
+
outputSchema: mediaMetadataShape,
|
|
67
149
|
annotations: readOnlyHint
|
|
68
150
|
},
|
|
69
151
|
async ({ input }) => safely(async () => inspectMedia(input))
|
|
@@ -72,6 +154,22 @@ function createMcpServer() {
|
|
|
72
154
|
"get_media_capabilities",
|
|
73
155
|
{
|
|
74
156
|
description: "Detect local FFmpeg capabilities.",
|
|
157
|
+
outputSchema: {
|
|
158
|
+
ffmpegVersion: z.string(),
|
|
159
|
+
encoders: z.object({
|
|
160
|
+
h264: z.boolean(),
|
|
161
|
+
hevc: z.boolean(),
|
|
162
|
+
av1: z.boolean(),
|
|
163
|
+
aac: z.boolean()
|
|
164
|
+
}),
|
|
165
|
+
hardwareAcceleration: z.array(z.string()),
|
|
166
|
+
filters: z.object({
|
|
167
|
+
scale: z.boolean(),
|
|
168
|
+
crop: z.boolean(),
|
|
169
|
+
concat: z.boolean(),
|
|
170
|
+
subtitles: z.boolean()
|
|
171
|
+
})
|
|
172
|
+
},
|
|
75
173
|
annotations: readOnlyHint
|
|
76
174
|
},
|
|
77
175
|
async () => safely(getCapabilities)
|
|
@@ -79,8 +177,9 @@ function createMcpServer() {
|
|
|
79
177
|
server.registerTool(
|
|
80
178
|
"plan_media",
|
|
81
179
|
{
|
|
82
|
-
description:
|
|
180
|
+
description: `Create an inspectable versioned semantic Media IR plan from semantic goals. All goals in the MediaGoals type are accepted. Plans conform to the canonical Media IR v${mediaPlanSchemaVersion} JSON Schema at ${mediaPlanSchemaId}, also returned by get_media_plan_schema.`,
|
|
83
181
|
inputSchema: { input: z.string().min(1), goals: goalSchema },
|
|
182
|
+
outputSchema: { plan: planSchema },
|
|
84
183
|
annotations: readOnlyHint
|
|
85
184
|
},
|
|
86
185
|
async ({ input, goals }) => safely(async () => ({
|
|
@@ -91,6 +190,80 @@ function createMcpServer() {
|
|
|
91
190
|
})
|
|
92
191
|
}))
|
|
93
192
|
);
|
|
193
|
+
server.registerTool(
|
|
194
|
+
"validate_plan",
|
|
195
|
+
{
|
|
196
|
+
description: `Detect mechanical plan issues (impossible trims, dimension conflicts, out-of-range timestamps, concatenation stream conflicts) against a real source without executing. Plans conform to the canonical Media IR v${mediaPlanSchemaVersion} JSON Schema at ${mediaPlanSchemaId}. Read-only.`,
|
|
197
|
+
inputSchema: {
|
|
198
|
+
plan: z.union([z.string().min(1), planRefSchema])
|
|
199
|
+
},
|
|
200
|
+
outputSchema: {
|
|
201
|
+
issues: z.array(
|
|
202
|
+
z.object({
|
|
203
|
+
field: z.string(),
|
|
204
|
+
message: z.string(),
|
|
205
|
+
repairable: z.boolean(),
|
|
206
|
+
normalization: z.array(
|
|
207
|
+
z.object({
|
|
208
|
+
input: z.string(),
|
|
209
|
+
differences: z.array(z.string()),
|
|
210
|
+
plan: planSchema
|
|
211
|
+
})
|
|
212
|
+
).optional()
|
|
213
|
+
})
|
|
214
|
+
)
|
|
215
|
+
},
|
|
216
|
+
annotations: readOnlyHint
|
|
217
|
+
},
|
|
218
|
+
async ({ plan: input }) => safely(async () => {
|
|
219
|
+
const plan = normalizePlan(input);
|
|
220
|
+
const source = await inspectMedia(plan.source.path);
|
|
221
|
+
const concatenationSources = await inspectConcatenationSources(plan, source);
|
|
222
|
+
return {
|
|
223
|
+
issues: inspectPlanIssues(plan, source, {
|
|
224
|
+
...concatenationSources === void 0 ? {} : { concatenationSources }
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
server.registerTool(
|
|
230
|
+
"repair_plan",
|
|
231
|
+
{
|
|
232
|
+
description: `Repair mechanical plan issues (clamp trims and timestamps into source duration, reconcile resize with aspect ratio) and return the repaired plan with a structured repair report. Plans conform to the canonical Media IR v${mediaPlanSchemaVersion} JSON Schema at ${mediaPlanSchemaId}.`,
|
|
233
|
+
inputSchema: {
|
|
234
|
+
plan: z.union([z.string().min(1), planRefSchema])
|
|
235
|
+
},
|
|
236
|
+
outputSchema: {
|
|
237
|
+
repairs: z.array(
|
|
238
|
+
z.object({
|
|
239
|
+
field: z.string(),
|
|
240
|
+
action: z.string(),
|
|
241
|
+
from: z.unknown(),
|
|
242
|
+
to: z.unknown()
|
|
243
|
+
})
|
|
244
|
+
),
|
|
245
|
+
repairedPlan: planSchema
|
|
246
|
+
},
|
|
247
|
+
annotations: readOnlyHint
|
|
248
|
+
},
|
|
249
|
+
async ({ plan: input }) => safely(async () => {
|
|
250
|
+
const plan = normalizePlan(input);
|
|
251
|
+
const { plan: repaired, repairs } = repairPlan(plan, await inspectMedia(plan.source.path));
|
|
252
|
+
return { repairs, repairedPlan: repaired };
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
server.registerTool(
|
|
256
|
+
"get_media_plan_schema",
|
|
257
|
+
{
|
|
258
|
+
description: `Return the canonical Media Plan JSON Schema (Media IR v${mediaPlanSchemaVersion}, ${mediaPlanSchemaId}) generated from the runtime models, so agent tooling cannot drift.`,
|
|
259
|
+
outputSchema: { $id: z.string(), schema: z.record(z.string(), z.unknown()) },
|
|
260
|
+
annotations: readOnlyHint
|
|
261
|
+
},
|
|
262
|
+
async () => safely(async () => {
|
|
263
|
+
const { mediaPlanJsonSchema } = await import("@hadialmarzooq/agent-media-core");
|
|
264
|
+
return { $id: mediaPlanSchemaId, schema: mediaPlanJsonSchema };
|
|
265
|
+
})
|
|
266
|
+
);
|
|
94
267
|
server.registerTool(
|
|
95
268
|
"make_vertical",
|
|
96
269
|
{
|
|
@@ -104,26 +277,32 @@ function createMcpServer() {
|
|
|
104
277
|
durationSeconds: z.number().positive().optional(),
|
|
105
278
|
maxSizeMB: z.number().positive().optional(),
|
|
106
279
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
107
|
-
overwrite: z.boolean().optional()
|
|
280
|
+
overwrite: z.boolean().optional(),
|
|
281
|
+
...contentCheckInputs
|
|
108
282
|
},
|
|
283
|
+
outputSchema: workflowResultShape,
|
|
109
284
|
annotations: destructiveHint
|
|
110
285
|
},
|
|
111
286
|
async (options, extra) => {
|
|
112
287
|
const notifications = mcpProgress(extra);
|
|
113
288
|
const response = await safely(
|
|
114
|
-
async () =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
289
|
+
async () => workflowResponse(
|
|
290
|
+
await makeVertical({
|
|
291
|
+
input: options.input,
|
|
292
|
+
output: options.output,
|
|
293
|
+
...options.width === void 0 ? {} : { width: options.width },
|
|
294
|
+
...options.height === void 0 ? {} : { height: options.height },
|
|
295
|
+
...options.trimStartSeconds === void 0 ? {} : { trimStartSeconds: options.trimStartSeconds },
|
|
296
|
+
...options.durationSeconds === void 0 ? {} : { durationSeconds: options.durationSeconds },
|
|
297
|
+
...options.maxSizeMB === void 0 ? {} : { maxSizeMB: options.maxSizeMB },
|
|
298
|
+
...options.audio === void 0 ? {} : { audio: options.audio },
|
|
299
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
300
|
+
...contentCheckOptions(options.contentChecks),
|
|
301
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
302
|
+
signal: extra.signal,
|
|
303
|
+
onProgress: notifications.notify
|
|
304
|
+
})
|
|
305
|
+
)
|
|
127
306
|
);
|
|
128
307
|
await notifications.drain();
|
|
129
308
|
return response;
|
|
@@ -141,25 +320,31 @@ function createMcpServer() {
|
|
|
141
320
|
maxSizeMB: z.number().positive().optional(),
|
|
142
321
|
quality: z.enum(["high", "balanced", "small"]).optional(),
|
|
143
322
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
144
|
-
overwrite: z.boolean().optional()
|
|
323
|
+
overwrite: z.boolean().optional(),
|
|
324
|
+
...contentCheckInputs
|
|
145
325
|
},
|
|
326
|
+
outputSchema: workflowResultShape,
|
|
146
327
|
annotations: destructiveHint
|
|
147
328
|
},
|
|
148
329
|
async (options, extra) => {
|
|
149
330
|
const notifications = mcpProgress(extra);
|
|
150
331
|
const response = await safely(
|
|
151
|
-
async () =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
332
|
+
async () => workflowResponse(
|
|
333
|
+
await optimizeForWeb({
|
|
334
|
+
input: options.input,
|
|
335
|
+
output: options.output,
|
|
336
|
+
...options.trimStartSeconds === void 0 ? {} : { trimStartSeconds: options.trimStartSeconds },
|
|
337
|
+
...options.durationSeconds === void 0 ? {} : { durationSeconds: options.durationSeconds },
|
|
338
|
+
...options.maxSizeMB === void 0 ? {} : { maxSizeMB: options.maxSizeMB },
|
|
339
|
+
...options.quality === void 0 ? {} : { quality: options.quality },
|
|
340
|
+
...options.audio === void 0 ? {} : { audio: options.audio },
|
|
341
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
342
|
+
...contentCheckOptions(options.contentChecks),
|
|
343
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
344
|
+
signal: extra.signal,
|
|
345
|
+
onProgress: notifications.notify
|
|
346
|
+
})
|
|
347
|
+
)
|
|
163
348
|
);
|
|
164
349
|
await notifications.drain();
|
|
165
350
|
return response;
|
|
@@ -175,23 +360,29 @@ function createMcpServer() {
|
|
|
175
360
|
trimStartSeconds: z.number().nonnegative().optional(),
|
|
176
361
|
durationSeconds: z.number().positive().optional(),
|
|
177
362
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
178
|
-
overwrite: z.boolean().optional()
|
|
363
|
+
overwrite: z.boolean().optional(),
|
|
364
|
+
...contentCheckInputs
|
|
179
365
|
},
|
|
366
|
+
outputSchema: workflowResultShape,
|
|
180
367
|
annotations: destructiveHint
|
|
181
368
|
},
|
|
182
369
|
async (options, extra) => {
|
|
183
370
|
const notifications = mcpProgress(extra);
|
|
184
371
|
const response = await safely(
|
|
185
|
-
async () =>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
372
|
+
async () => workflowResponse(
|
|
373
|
+
await normalize({
|
|
374
|
+
input: options.input,
|
|
375
|
+
output: options.output,
|
|
376
|
+
...options.trimStartSeconds === void 0 ? {} : { trimStartSeconds: options.trimStartSeconds },
|
|
377
|
+
...options.durationSeconds === void 0 ? {} : { durationSeconds: options.durationSeconds },
|
|
378
|
+
...options.audio === void 0 ? {} : { audio: options.audio },
|
|
379
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
380
|
+
...contentCheckOptions(options.contentChecks),
|
|
381
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
382
|
+
signal: extra.signal,
|
|
383
|
+
onProgress: notifications.notify
|
|
384
|
+
})
|
|
385
|
+
)
|
|
195
386
|
);
|
|
196
387
|
await notifications.drain();
|
|
197
388
|
return response;
|
|
@@ -207,23 +398,29 @@ function createMcpServer() {
|
|
|
207
398
|
format: z.enum(["m4a", "mp3", "wav"]).optional(),
|
|
208
399
|
trimStartSeconds: z.number().nonnegative().optional(),
|
|
209
400
|
durationSeconds: z.number().positive().optional(),
|
|
210
|
-
overwrite: z.boolean().optional()
|
|
401
|
+
overwrite: z.boolean().optional(),
|
|
402
|
+
...contentCheckInputs
|
|
211
403
|
},
|
|
404
|
+
outputSchema: workflowResultShape,
|
|
212
405
|
annotations: destructiveHint
|
|
213
406
|
},
|
|
214
407
|
async (options, extra) => {
|
|
215
408
|
const notifications = mcpProgress(extra);
|
|
216
409
|
const response = await safely(
|
|
217
|
-
async () =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
410
|
+
async () => workflowResponse(
|
|
411
|
+
await extractAudio({
|
|
412
|
+
input: options.input,
|
|
413
|
+
output: options.output,
|
|
414
|
+
...options.format === void 0 ? {} : { format: options.format },
|
|
415
|
+
...options.trimStartSeconds === void 0 ? {} : { trimStartSeconds: options.trimStartSeconds },
|
|
416
|
+
...options.durationSeconds === void 0 ? {} : { durationSeconds: options.durationSeconds },
|
|
417
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
418
|
+
...contentCheckOptions(options.contentChecks),
|
|
419
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
420
|
+
signal: extra.signal,
|
|
421
|
+
onProgress: notifications.notify
|
|
422
|
+
})
|
|
423
|
+
)
|
|
227
424
|
);
|
|
228
425
|
await notifications.drain();
|
|
229
426
|
return response;
|
|
@@ -238,22 +435,28 @@ function createMcpServer() {
|
|
|
238
435
|
output: z.string().min(1),
|
|
239
436
|
atSeconds: z.number().nonnegative().optional(),
|
|
240
437
|
format: z.enum(["jpg", "png"]).optional(),
|
|
241
|
-
overwrite: z.boolean().optional()
|
|
438
|
+
overwrite: z.boolean().optional(),
|
|
439
|
+
...contentCheckInputs
|
|
242
440
|
},
|
|
441
|
+
outputSchema: workflowResultShape,
|
|
243
442
|
annotations: destructiveHint
|
|
244
443
|
},
|
|
245
444
|
async (options, extra) => {
|
|
246
445
|
const notifications = mcpProgress(extra);
|
|
247
446
|
const response = await safely(
|
|
248
|
-
async () =>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
447
|
+
async () => workflowResponse(
|
|
448
|
+
await extractFrame({
|
|
449
|
+
input: options.input,
|
|
450
|
+
output: options.output,
|
|
451
|
+
...options.atSeconds === void 0 ? {} : { atSeconds: options.atSeconds },
|
|
452
|
+
...options.format === void 0 ? {} : { format: options.format },
|
|
453
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
454
|
+
...contentCheckOptions(options.contentChecks),
|
|
455
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
456
|
+
signal: extra.signal,
|
|
457
|
+
onProgress: notifications.notify
|
|
458
|
+
})
|
|
459
|
+
)
|
|
257
460
|
);
|
|
258
461
|
await notifications.drain();
|
|
259
462
|
return response;
|
|
@@ -262,26 +465,30 @@ function createMcpServer() {
|
|
|
262
465
|
server.registerTool(
|
|
263
466
|
"concatenate_media",
|
|
264
467
|
{
|
|
265
|
-
description: "Concatenate
|
|
468
|
+
description: "Concatenate two or more media sources into a single verified output. Pass every clip in `inputs`, in playback order. All inputs must have compatible stream layouts. Reports MCP progress when requested. Overwrite is destructive.",
|
|
266
469
|
inputSchema: {
|
|
267
|
-
|
|
268
|
-
inputs: z.array(z.string().min(1)).min(1),
|
|
470
|
+
inputs: z.array(z.string().min(1)).min(2).describe("Every clip to join, in playback order. The first entry leads the output."),
|
|
269
471
|
output: z.string().min(1),
|
|
270
|
-
overwrite: z.boolean().optional()
|
|
472
|
+
overwrite: z.boolean().optional(),
|
|
473
|
+
...contentCheckInputs
|
|
271
474
|
},
|
|
475
|
+
outputSchema: workflowResultShape,
|
|
272
476
|
annotations: destructiveHint
|
|
273
477
|
},
|
|
274
478
|
async (options, extra) => {
|
|
275
479
|
const notifications = mcpProgress(extra);
|
|
276
480
|
const response = await safely(
|
|
277
|
-
async () =>
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
481
|
+
async () => workflowResponse(
|
|
482
|
+
await concatenate({
|
|
483
|
+
inputs: options.inputs,
|
|
484
|
+
output: options.output,
|
|
485
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
486
|
+
...contentCheckOptions(options.contentChecks),
|
|
487
|
+
...options.warnOnly === void 0 ? {} : { warnOnly: options.warnOnly },
|
|
488
|
+
signal: extra.signal,
|
|
489
|
+
onProgress: notifications.notify
|
|
490
|
+
})
|
|
491
|
+
)
|
|
285
492
|
);
|
|
286
493
|
await notifications.drain();
|
|
287
494
|
return response;
|
|
@@ -290,33 +497,102 @@ function createMcpServer() {
|
|
|
290
497
|
server.registerTool(
|
|
291
498
|
"execute_media_plan",
|
|
292
499
|
{
|
|
293
|
-
description:
|
|
500
|
+
description: `Execute a serialized semantic Media IR plan conforming to the canonical Media IR v${mediaPlanSchemaVersion} JSON Schema at ${mediaPlanSchemaId}. Accepts a plan object or JSON string. Fails with VERIFICATION_FAILED when the output does not satisfy the plan. Overwrite is destructive. Set writeReceipt to emit a durable receipt, or resume to skip execution when a passing receipt already matches the plan and source.`,
|
|
294
501
|
inputSchema: {
|
|
295
502
|
plan: z.union([z.string().min(1), planRefSchema]),
|
|
296
503
|
output: z.string().min(1),
|
|
297
|
-
overwrite: z.boolean().optional()
|
|
504
|
+
overwrite: z.boolean().optional(),
|
|
505
|
+
writeReceipt: z.boolean().optional(),
|
|
506
|
+
resume: z.boolean().optional(),
|
|
507
|
+
...contentCheckInputs
|
|
508
|
+
},
|
|
509
|
+
outputSchema: {
|
|
510
|
+
output: z.string(),
|
|
511
|
+
resumed: z.boolean().optional(),
|
|
512
|
+
receipt: receiptSchema.optional(),
|
|
513
|
+
verification: verificationSchema
|
|
298
514
|
},
|
|
299
515
|
annotations: destructiveHint
|
|
300
516
|
},
|
|
301
|
-
async ({ plan: input, output, overwrite }, extra) => {
|
|
517
|
+
async ({ plan: input, output, overwrite, writeReceipt, resume, contentChecks, warnOnly }, extra) => {
|
|
302
518
|
const notifications = mcpProgress(extra);
|
|
303
519
|
const response = await safely(async () => {
|
|
304
520
|
const plan = normalizePlan(input);
|
|
305
521
|
const execution = await executePlan(plan, {
|
|
306
522
|
output,
|
|
307
523
|
...overwrite === void 0 ? {} : { overwrite },
|
|
524
|
+
...writeReceipt === void 0 ? {} : { writeReceipt },
|
|
525
|
+
...resume === void 0 ? {} : { resume },
|
|
526
|
+
...contentCheckOptions(contentChecks),
|
|
527
|
+
...warnOnly === void 0 ? {} : { warnOnly },
|
|
308
528
|
signal: extra.signal,
|
|
309
529
|
onProgress: notifications.notify
|
|
310
530
|
});
|
|
531
|
+
const verification = execution.verification ?? execution.receipt?.verification ?? verifyMedia(await inspectMedia(execution.output), plan.expectations);
|
|
532
|
+
if (!verification.passed) {
|
|
533
|
+
throw new MediaError({
|
|
534
|
+
code: "VERIFICATION_FAILED",
|
|
535
|
+
message: "The plan executed, but the output did not satisfy its expectations.",
|
|
536
|
+
context: { output: execution.output, verification },
|
|
537
|
+
suggestedActions: ["Inspect the failed checks, adjust the plan, and retry."]
|
|
538
|
+
});
|
|
539
|
+
}
|
|
311
540
|
return {
|
|
312
541
|
output: execution.output,
|
|
313
|
-
|
|
542
|
+
...execution.resumed === void 0 ? {} : { resumed: execution.resumed },
|
|
543
|
+
...execution.receipt === void 0 ? {} : { receipt: execution.receipt },
|
|
544
|
+
verification
|
|
314
545
|
};
|
|
315
546
|
});
|
|
316
547
|
await notifications.drain();
|
|
317
548
|
return response;
|
|
318
549
|
}
|
|
319
550
|
);
|
|
551
|
+
server.registerTool(
|
|
552
|
+
"resume_execution",
|
|
553
|
+
{
|
|
554
|
+
description: "Continue from a saved execution receipt: skip the work when the recorded output still satisfies the same plan against an unchanged source, and re-execute the plan when it does not. Overwrite is destructive.",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
receipt: z.string().min(1),
|
|
557
|
+
output: z.string().min(1).optional(),
|
|
558
|
+
overwrite: z.boolean().optional()
|
|
559
|
+
},
|
|
560
|
+
outputSchema: {
|
|
561
|
+
output: z.string(),
|
|
562
|
+
resumed: z.boolean(),
|
|
563
|
+
receipt: receiptSchema.optional()
|
|
564
|
+
},
|
|
565
|
+
annotations: destructiveHint
|
|
566
|
+
},
|
|
567
|
+
async ({ receipt, output, overwrite }, extra) => {
|
|
568
|
+
const notifications = mcpProgress(extra);
|
|
569
|
+
const response = await safely(async () => {
|
|
570
|
+
const execution = await resumeFromReceipt(parseReceipt(receipt), {
|
|
571
|
+
...output === void 0 ? {} : { output },
|
|
572
|
+
...overwrite === void 0 ? {} : { overwrite },
|
|
573
|
+
signal: extra.signal,
|
|
574
|
+
onProgress: notifications.notify
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
output: execution.output,
|
|
578
|
+
resumed: execution.resumed === true,
|
|
579
|
+
...execution.receipt === void 0 ? {} : { receipt: execution.receipt }
|
|
580
|
+
};
|
|
581
|
+
});
|
|
582
|
+
await notifications.drain();
|
|
583
|
+
return response;
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
server.registerTool(
|
|
587
|
+
"inspect_receipt",
|
|
588
|
+
{
|
|
589
|
+
description: "Validate and inspect a saved execution receipt (durable record of plan, source fingerprint, output, and verification).",
|
|
590
|
+
inputSchema: { receipt: z.string().min(1) },
|
|
591
|
+
outputSchema: { receipt: receiptSchema },
|
|
592
|
+
annotations: readOnlyHint
|
|
593
|
+
},
|
|
594
|
+
async ({ receipt }) => safely(async () => ({ receipt: parseReceipt(receipt) }))
|
|
595
|
+
);
|
|
320
596
|
server.registerTool(
|
|
321
597
|
"verify_media",
|
|
322
598
|
{
|
|
@@ -325,6 +601,7 @@ function createMcpServer() {
|
|
|
325
601
|
output: z.string().min(1),
|
|
326
602
|
plan: z.union([z.string().min(1), planRefSchema])
|
|
327
603
|
},
|
|
604
|
+
outputSchema: verificationShape,
|
|
328
605
|
annotations: readOnlyHint
|
|
329
606
|
},
|
|
330
607
|
async ({ output, plan: input }) => safely(async () => {
|
|
@@ -334,8 +611,17 @@ function createMcpServer() {
|
|
|
334
611
|
);
|
|
335
612
|
return server;
|
|
336
613
|
}
|
|
614
|
+
function workflowResponse(result2) {
|
|
615
|
+
const rest = { ...result2 };
|
|
616
|
+
delete rest.serializedPlan;
|
|
617
|
+
return rest;
|
|
618
|
+
}
|
|
337
619
|
function result(value) {
|
|
338
|
-
|
|
620
|
+
const content = [{ type: "text", text: JSON.stringify(value) }];
|
|
621
|
+
return isPlainObject(value) ? { content, structuredContent: value } : { content };
|
|
622
|
+
}
|
|
623
|
+
function isPlainObject(value) {
|
|
624
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
339
625
|
}
|
|
340
626
|
async function safely(operation) {
|
|
341
627
|
try {
|
|
@@ -345,8 +631,20 @@ async function safely(operation) {
|
|
|
345
631
|
code: "UNEXPECTED_ERROR",
|
|
346
632
|
message: error instanceof Error ? error.message : String(error)
|
|
347
633
|
};
|
|
348
|
-
return {
|
|
634
|
+
return {
|
|
635
|
+
content: [{ type: "text", text: JSON.stringify(structured) }],
|
|
636
|
+
isError: true
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async function inspectConcatenationSources(plan, source) {
|
|
641
|
+
const concatenate2 = plan.steps.find((step) => step.operation === "concatenate");
|
|
642
|
+
if (concatenate2?.operation !== "concatenate") return void 0;
|
|
643
|
+
const sources = [];
|
|
644
|
+
for (const [index, input] of concatenate2.inputs.entries()) {
|
|
645
|
+
sources.push(index === 0 ? source : await inspectMedia(input));
|
|
349
646
|
}
|
|
647
|
+
return sources;
|
|
350
648
|
}
|
|
351
649
|
function normalizePlan(input) {
|
|
352
650
|
return typeof input === "string" ? parsePlan(input) : validatePlan(input);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hadialmarzooq/agent-media-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for semantic, replayable, and verified media transformations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"media",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
41
41
|
"zod": "^4.5.4",
|
|
42
|
-
"@hadialmarzooq/agent-media-core": "0.
|
|
43
|
-
"@hadialmarzooq/agent-media-ffmpeg": "0.
|
|
42
|
+
"@hadialmarzooq/agent-media-core": "0.3.0",
|
|
43
|
+
"@hadialmarzooq/agent-media-ffmpeg": "0.3.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup src/index.ts --format esm --dts",
|