@hadialmarzooq/agent-media-mcp 0.1.0 → 0.2.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/README.md +72 -0
- package/dist/index.js +88 -28
- package/package.json +6 -4
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @hadialmarzooq/agent-media-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for semantic, replayable, and verified media transformations.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) stdio server that exposes [Agent Media](https://github.com/HadiAlMarzooq/agent-media) to AI agents. Ten semantic tools covering the full inspect → plan → execute → verify contract.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @hadialmarzooq/agent-media-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Prerequisites: Node.js 22+, `ffmpeg` and `ffprobe` on `PATH`.
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
| Tool | Description |
|
|
20
|
+
| ------------------------ | ------------------------------------------------------- |
|
|
21
|
+
| `inspect_media` | Inspect normalized media metadata |
|
|
22
|
+
| `get_media_capabilities` | Detect local FFmpeg capabilities |
|
|
23
|
+
| `plan_media` | Create an inspectable versioned Media IR plan |
|
|
24
|
+
| `make_vertical` | Verified 9:16 vertical workflow with progress |
|
|
25
|
+
| `optimize_for_web` | Verified web optimization workflow with progress |
|
|
26
|
+
| `normalize_media` | Verified high-compatibility normalization with progress |
|
|
27
|
+
| `extract_audio` | Verified audio extraction with progress |
|
|
28
|
+
| `extract_frame` | Verified still frame extraction with progress |
|
|
29
|
+
| `execute_media_plan` | Execute a serialized or object Media IR plan |
|
|
30
|
+
| `verify_media` | Verify output against plan expectations |
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Claude Desktop
|
|
35
|
+
|
|
36
|
+
Add to `claude_desktop_config.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"agent-media": {
|
|
42
|
+
"command": "agent-media-mcp"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Any MCP client
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
agent-media-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The server runs over stdio. Workflows send standard MCP `notifications/progress` when the client supplies a progress token. Plan execution and verification accept either a plan object or serialized plan JSON. Tool failures set `isError: true` and carry the same structured error body as the SDK and CLI.
|
|
55
|
+
|
|
56
|
+
## Why this instead of raw FFmpeg MCP wrappers
|
|
57
|
+
|
|
58
|
+
- **5 semantic workflows** instead of 40+ FFmpeg-shaped tools
|
|
59
|
+
- **Verification** — outputs are inspected and checked against plan expectations, not just "exit code 0"
|
|
60
|
+
- **Portable plans** — serialize, persist, replay, audit
|
|
61
|
+
- **Structured errors** — stable codes with recovery suggestions, not stderr noise
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
- [Full docs](https://github.com/HadiAlMarzooq/agent-media/tree/main/docs)
|
|
66
|
+
- [Workflows](https://github.com/HadiAlMarzooq/agent-media/blob/main/docs/workflows.md)
|
|
67
|
+
- [API reference](https://github.com/HadiAlMarzooq/agent-media/blob/main/docs/api.md)
|
|
68
|
+
- [Errors and recovery](https://github.com/HadiAlMarzooq/agent-media/blob/main/docs/errors.md)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -8,26 +8,27 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
9
|
import {
|
|
10
10
|
MediaError,
|
|
11
|
-
mediaPlanSchema,
|
|
12
11
|
parsePlan,
|
|
13
12
|
planMedia,
|
|
14
13
|
validatePlan,
|
|
15
14
|
verifyMedia
|
|
16
15
|
} from "@hadialmarzooq/agent-media-core";
|
|
17
16
|
import {
|
|
17
|
+
concatenate,
|
|
18
18
|
executePlan,
|
|
19
|
+
extractAudio,
|
|
20
|
+
extractFrame,
|
|
19
21
|
getCapabilities,
|
|
20
22
|
inspectMedia,
|
|
21
23
|
makeVertical,
|
|
22
|
-
optimizeForWeb,
|
|
23
24
|
normalize,
|
|
24
|
-
|
|
25
|
-
extractFrame
|
|
25
|
+
optimizeForWeb
|
|
26
26
|
} from "@hadialmarzooq/agent-media-ffmpeg";
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
var packageVersion = createRequire(import.meta.url)("../package.json").version;
|
|
29
29
|
var goalSchema = z.object({
|
|
30
30
|
trimStartSeconds: z.number().nonnegative().optional(),
|
|
31
|
+
trimEndSeconds: z.number().finite().optional(),
|
|
31
32
|
durationSeconds: z.number().positive().optional(),
|
|
32
33
|
aspectRatio: z.string().optional(),
|
|
33
34
|
width: z.number().int().positive().optional(),
|
|
@@ -35,33 +36,57 @@ var goalSchema = z.object({
|
|
|
35
36
|
maxSizeMB: z.number().positive().optional(),
|
|
36
37
|
compatibility: z.enum(["high", "balanced"]).optional(),
|
|
37
38
|
quality: z.enum(["high", "balanced", "small"]).optional(),
|
|
38
|
-
audio: z.enum(["preserve", "remove"]).optional()
|
|
39
|
-
})
|
|
39
|
+
audio: z.enum(["preserve", "remove"]).optional(),
|
|
40
|
+
extractAudio: z.object({ format: z.enum(["m4a", "mp3", "wav"]).optional() }).optional(),
|
|
41
|
+
extractFrame: z.object({
|
|
42
|
+
atSeconds: z.number().nonnegative().optional(),
|
|
43
|
+
format: z.enum(["jpg", "png"]).optional()
|
|
44
|
+
}).optional(),
|
|
45
|
+
concatenate: z.array(z.string().min(1)).min(1).optional()
|
|
46
|
+
}).strict();
|
|
47
|
+
function validateGoalSchema(goals) {
|
|
48
|
+
if (!Object.values(goals).some((v) => v !== void 0)) {
|
|
49
|
+
throw new MediaError({
|
|
50
|
+
code: "INVALID_PLAN",
|
|
51
|
+
message: "At least one goal must be provided. An empty goals object produces a no-op plan.",
|
|
52
|
+
suggestedActions: ["Provide at least one semantic goal."]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return goals;
|
|
56
|
+
}
|
|
57
|
+
var planRefSchema = z.object({ irVersion: z.literal("1"), source: z.object({ path: z.string() }) }).passthrough();
|
|
58
|
+
var readOnlyHint = { readOnlyHint: true };
|
|
59
|
+
var destructiveHint = { destructiveHint: true };
|
|
40
60
|
function createMcpServer() {
|
|
41
61
|
const server = new McpServer({ name: "agent-media", version: packageVersion });
|
|
42
62
|
server.registerTool(
|
|
43
63
|
"inspect_media",
|
|
44
64
|
{
|
|
45
|
-
description: "Inspect normalized media metadata.",
|
|
46
|
-
inputSchema: { input: z.string().min(1) }
|
|
65
|
+
description: "Inspect normalized media metadata. Paths resolve against the server working directory; absolute paths are recommended.",
|
|
66
|
+
inputSchema: { input: z.string().min(1) },
|
|
67
|
+
annotations: readOnlyHint
|
|
47
68
|
},
|
|
48
69
|
async ({ input }) => safely(async () => inspectMedia(input))
|
|
49
70
|
);
|
|
50
71
|
server.registerTool(
|
|
51
72
|
"get_media_capabilities",
|
|
52
|
-
{
|
|
73
|
+
{
|
|
74
|
+
description: "Detect local FFmpeg capabilities.",
|
|
75
|
+
annotations: readOnlyHint
|
|
76
|
+
},
|
|
53
77
|
async () => safely(getCapabilities)
|
|
54
78
|
);
|
|
55
79
|
server.registerTool(
|
|
56
80
|
"plan_media",
|
|
57
81
|
{
|
|
58
|
-
description: "Create an inspectable versioned semantic Media IR plan.",
|
|
59
|
-
inputSchema: { input: z.string().min(1), goals: goalSchema }
|
|
82
|
+
description: "Create an inspectable versioned semantic Media IR plan from semantic goals. All goals in the MediaGoals type are accepted.",
|
|
83
|
+
inputSchema: { input: z.string().min(1), goals: goalSchema },
|
|
84
|
+
annotations: readOnlyHint
|
|
60
85
|
},
|
|
61
86
|
async ({ input, goals }) => safely(async () => ({
|
|
62
87
|
plan: planMedia({
|
|
63
88
|
source: await inspectMedia(input),
|
|
64
|
-
goals: cleanGoals(goals),
|
|
89
|
+
goals: cleanGoals(validateGoalSchema(goals)),
|
|
65
90
|
capabilities: await getCapabilities()
|
|
66
91
|
})
|
|
67
92
|
}))
|
|
@@ -69,7 +94,7 @@ function createMcpServer() {
|
|
|
69
94
|
server.registerTool(
|
|
70
95
|
"make_vertical",
|
|
71
96
|
{
|
|
72
|
-
description: "Inspect, plan, execute, and verify a high-compatibility 9:16 video. Reports MCP progress when requested.",
|
|
97
|
+
description: "Inspect, plan, execute, and verify a high-compatibility 9:16 video. Reports MCP progress when requested. Overwrite is destructive.",
|
|
73
98
|
inputSchema: {
|
|
74
99
|
input: z.string().min(1),
|
|
75
100
|
output: z.string().min(1),
|
|
@@ -80,7 +105,8 @@ function createMcpServer() {
|
|
|
80
105
|
maxSizeMB: z.number().positive().optional(),
|
|
81
106
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
82
107
|
overwrite: z.boolean().optional()
|
|
83
|
-
}
|
|
108
|
+
},
|
|
109
|
+
annotations: destructiveHint
|
|
84
110
|
},
|
|
85
111
|
async (options, extra) => {
|
|
86
112
|
const notifications = mcpProgress(extra);
|
|
@@ -106,7 +132,7 @@ function createMcpServer() {
|
|
|
106
132
|
server.registerTool(
|
|
107
133
|
"optimize_for_web",
|
|
108
134
|
{
|
|
109
|
-
description: "Inspect, plan, execute, and verify a web-optimized high-compatibility video. Reports MCP progress when requested.",
|
|
135
|
+
description: "Inspect, plan, execute, and verify a web-optimized high-compatibility video. Reports MCP progress when requested. Overwrite is destructive.",
|
|
110
136
|
inputSchema: {
|
|
111
137
|
input: z.string().min(1),
|
|
112
138
|
output: z.string().min(1),
|
|
@@ -116,7 +142,8 @@ function createMcpServer() {
|
|
|
116
142
|
quality: z.enum(["high", "balanced", "small"]).optional(),
|
|
117
143
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
118
144
|
overwrite: z.boolean().optional()
|
|
119
|
-
}
|
|
145
|
+
},
|
|
146
|
+
annotations: destructiveHint
|
|
120
147
|
},
|
|
121
148
|
async (options, extra) => {
|
|
122
149
|
const notifications = mcpProgress(extra);
|
|
@@ -141,7 +168,7 @@ function createMcpServer() {
|
|
|
141
168
|
server.registerTool(
|
|
142
169
|
"normalize_media",
|
|
143
170
|
{
|
|
144
|
-
description: "Inspect, plan, execute, and verify a normalized high-compatibility copy (H.264, yuv420p, faststart). Reports MCP progress when requested.",
|
|
171
|
+
description: "Inspect, plan, execute, and verify a normalized high-compatibility copy (H.264, yuv420p, faststart). Reports MCP progress when requested. Overwrite is destructive.",
|
|
145
172
|
inputSchema: {
|
|
146
173
|
input: z.string().min(1),
|
|
147
174
|
output: z.string().min(1),
|
|
@@ -149,7 +176,8 @@ function createMcpServer() {
|
|
|
149
176
|
durationSeconds: z.number().positive().optional(),
|
|
150
177
|
audio: z.enum(["preserve", "remove"]).optional(),
|
|
151
178
|
overwrite: z.boolean().optional()
|
|
152
|
-
}
|
|
179
|
+
},
|
|
180
|
+
annotations: destructiveHint
|
|
153
181
|
},
|
|
154
182
|
async (options, extra) => {
|
|
155
183
|
const notifications = mcpProgress(extra);
|
|
@@ -172,7 +200,7 @@ function createMcpServer() {
|
|
|
172
200
|
server.registerTool(
|
|
173
201
|
"extract_audio",
|
|
174
202
|
{
|
|
175
|
-
description: "Extract and verify audio from any media source. Reports MCP progress when requested.",
|
|
203
|
+
description: "Extract and verify audio from any media source. Reports MCP progress when requested. Overwrite is destructive.",
|
|
176
204
|
inputSchema: {
|
|
177
205
|
input: z.string().min(1),
|
|
178
206
|
output: z.string().min(1),
|
|
@@ -180,7 +208,8 @@ function createMcpServer() {
|
|
|
180
208
|
trimStartSeconds: z.number().nonnegative().optional(),
|
|
181
209
|
durationSeconds: z.number().positive().optional(),
|
|
182
210
|
overwrite: z.boolean().optional()
|
|
183
|
-
}
|
|
211
|
+
},
|
|
212
|
+
annotations: destructiveHint
|
|
184
213
|
},
|
|
185
214
|
async (options, extra) => {
|
|
186
215
|
const notifications = mcpProgress(extra);
|
|
@@ -203,14 +232,15 @@ function createMcpServer() {
|
|
|
203
232
|
server.registerTool(
|
|
204
233
|
"extract_frame",
|
|
205
234
|
{
|
|
206
|
-
description: "Extract and verify a still frame from a video source. Reports MCP progress when requested.",
|
|
235
|
+
description: "Extract and verify a still frame from a video source. The timestamp must be within the source duration. Reports MCP progress when requested. Overwrite is destructive.",
|
|
207
236
|
inputSchema: {
|
|
208
237
|
input: z.string().min(1),
|
|
209
238
|
output: z.string().min(1),
|
|
210
239
|
atSeconds: z.number().nonnegative().optional(),
|
|
211
240
|
format: z.enum(["jpg", "png"]).optional(),
|
|
212
241
|
overwrite: z.boolean().optional()
|
|
213
|
-
}
|
|
242
|
+
},
|
|
243
|
+
annotations: destructiveHint
|
|
214
244
|
},
|
|
215
245
|
async (options, extra) => {
|
|
216
246
|
const notifications = mcpProgress(extra);
|
|
@@ -229,15 +259,44 @@ function createMcpServer() {
|
|
|
229
259
|
return response;
|
|
230
260
|
}
|
|
231
261
|
);
|
|
262
|
+
server.registerTool(
|
|
263
|
+
"concatenate_media",
|
|
264
|
+
{
|
|
265
|
+
description: "Concatenate multiple media sources into a single verified output. All inputs must have compatible stream layouts. Reports MCP progress when requested. Overwrite is destructive.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
input: z.string().min(1),
|
|
268
|
+
inputs: z.array(z.string().min(1)).min(1),
|
|
269
|
+
output: z.string().min(1),
|
|
270
|
+
overwrite: z.boolean().optional()
|
|
271
|
+
},
|
|
272
|
+
annotations: destructiveHint
|
|
273
|
+
},
|
|
274
|
+
async (options, extra) => {
|
|
275
|
+
const notifications = mcpProgress(extra);
|
|
276
|
+
const response = await safely(
|
|
277
|
+
async () => concatenate({
|
|
278
|
+
input: options.input,
|
|
279
|
+
inputs: options.inputs,
|
|
280
|
+
output: options.output,
|
|
281
|
+
...options.overwrite === void 0 ? {} : { overwrite: options.overwrite },
|
|
282
|
+
signal: extra.signal,
|
|
283
|
+
onProgress: notifications.notify
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
await notifications.drain();
|
|
287
|
+
return response;
|
|
288
|
+
}
|
|
289
|
+
);
|
|
232
290
|
server.registerTool(
|
|
233
291
|
"execute_media_plan",
|
|
234
292
|
{
|
|
235
|
-
description: "Execute a serialized semantic Media IR plan.",
|
|
293
|
+
description: "Execute a serialized semantic Media IR plan. Accepts a plan object or JSON string. Overwrite is destructive.",
|
|
236
294
|
inputSchema: {
|
|
237
|
-
plan: z.union([z.string().min(1),
|
|
295
|
+
plan: z.union([z.string().min(1), planRefSchema]),
|
|
238
296
|
output: z.string().min(1),
|
|
239
297
|
overwrite: z.boolean().optional()
|
|
240
|
-
}
|
|
298
|
+
},
|
|
299
|
+
annotations: destructiveHint
|
|
241
300
|
},
|
|
242
301
|
async ({ plan: input, output, overwrite }, extra) => {
|
|
243
302
|
const notifications = mcpProgress(extra);
|
|
@@ -261,11 +320,12 @@ function createMcpServer() {
|
|
|
261
320
|
server.registerTool(
|
|
262
321
|
"verify_media",
|
|
263
322
|
{
|
|
264
|
-
description: "Verify output media against a serialized semantic Media IR plan.",
|
|
323
|
+
description: "Verify output media against a serialized semantic Media IR plan. Accepts a plan object or JSON string.",
|
|
265
324
|
inputSchema: {
|
|
266
325
|
output: z.string().min(1),
|
|
267
|
-
plan: z.union([z.string().min(1),
|
|
268
|
-
}
|
|
326
|
+
plan: z.union([z.string().min(1), planRefSchema])
|
|
327
|
+
},
|
|
328
|
+
annotations: readOnlyHint
|
|
269
329
|
},
|
|
270
330
|
async ({ output, plan: input }) => safely(async () => {
|
|
271
331
|
const plan = normalizePlan(input);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hadialmarzooq/agent-media-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for semantic, replayable, and verified media transformations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"media",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"main": "./dist/index.js",
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"files": [
|
|
32
|
-
"dist"
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
33
35
|
],
|
|
34
36
|
"publishConfig": {
|
|
35
37
|
"access": "public"
|
|
@@ -37,8 +39,8 @@
|
|
|
37
39
|
"dependencies": {
|
|
38
40
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
39
41
|
"zod": "^4.5.4",
|
|
40
|
-
"@hadialmarzooq/agent-media-
|
|
41
|
-
"@hadialmarzooq/agent-media-
|
|
42
|
+
"@hadialmarzooq/agent-media-core": "0.2.0",
|
|
43
|
+
"@hadialmarzooq/agent-media-ffmpeg": "0.2.0"
|
|
42
44
|
},
|
|
43
45
|
"scripts": {
|
|
44
46
|
"build": "tsup src/index.ts --format esm --dts",
|