@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.
Files changed (3) hide show
  1. package/README.md +72 -0
  2. package/dist/index.js +88 -28
  3. 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
- extractAudio,
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
- { description: "Detect local FFmpeg capabilities." },
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), mediaPlanSchema]),
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), mediaPlanSchema])
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.1.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-ffmpeg": "0.1.0",
41
- "@hadialmarzooq/agent-media-core": "0.1.0"
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",