mcp-gif 1.0.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/build/index.js +83 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# mcp-gif
|
|
2
|
+
|
|
3
|
+
MCP server for GIF animation analysis. Provides metadata extraction, individual frame export, and per-frame timing information.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Claude Code
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
claude mcp add -s user gif npx mcp-gif
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to your `claude_desktop_config.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"gif": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["mcp-gif"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
### `gif_info`
|
|
31
|
+
|
|
32
|
+
Get GIF metadata: dimensions, frame count, delays, loop count, and average FPS.
|
|
33
|
+
|
|
34
|
+
**Input**: `path` — path to a GIF file
|
|
35
|
+
|
|
36
|
+
**Output**:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"width": 320,
|
|
40
|
+
"height": 240,
|
|
41
|
+
"frames": 12,
|
|
42
|
+
"delays": [100, 100, 100],
|
|
43
|
+
"loop": 0,
|
|
44
|
+
"avgFps": 10
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `gif_frame`
|
|
49
|
+
|
|
50
|
+
Extract a single frame as a PNG image.
|
|
51
|
+
|
|
52
|
+
**Input**: `path` — path to a GIF file, `frame` — 0-based frame index
|
|
53
|
+
|
|
54
|
+
**Output**: PNG image (base64)
|
|
55
|
+
|
|
56
|
+
### `gif_frames_list`
|
|
57
|
+
|
|
58
|
+
List all frames with their index, delay (ms), and FPS.
|
|
59
|
+
|
|
60
|
+
**Input**: `path` — path to a GIF file
|
|
61
|
+
|
|
62
|
+
**Output**:
|
|
63
|
+
```json
|
|
64
|
+
[
|
|
65
|
+
{ "index": 0, "delay": 100, "fps": 10 },
|
|
66
|
+
{ "index": 1, "delay": 200, "fps": 5 }
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
BSD-2-Clause
|
package/build/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "mcp-gif",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
});
|
|
10
|
+
server.tool("gif_info", "Get metadata of a GIF file: dimensions, frame count, delays, loop count, and average FPS", { path: z.string().describe("Path to the GIF file") }, async ({ path }) => {
|
|
11
|
+
const metadata = await sharp(path, { animated: true }).metadata();
|
|
12
|
+
const width = metadata.width ?? 0;
|
|
13
|
+
const height = metadata.pageHeight ?? metadata.height ?? 0;
|
|
14
|
+
const frames = metadata.pages ?? 1;
|
|
15
|
+
const delays = metadata.delay ?? [];
|
|
16
|
+
const loop = metadata.loop ?? 0;
|
|
17
|
+
const avgDelay = delays.length > 0
|
|
18
|
+
? delays.reduce((a, b) => a + b, 0) / delays.length
|
|
19
|
+
: 100;
|
|
20
|
+
const avgFps = avgDelay > 0 ? Math.round((1000 / avgDelay) * 100) / 100 : 0;
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify({ width, height, frames, delays, loop, avgFps }, null, 2),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
server.tool("gif_frame", "Extract a single frame from a GIF as a PNG image", {
|
|
31
|
+
path: z.string().describe("Path to the GIF file"),
|
|
32
|
+
frame: z.number().int().min(0).describe("Frame index (0-based)"),
|
|
33
|
+
}, async ({ path, frame }) => {
|
|
34
|
+
const metadata = await sharp(path, { animated: true }).metadata();
|
|
35
|
+
const totalFrames = metadata.pages ?? 1;
|
|
36
|
+
if (frame >= totalFrames) {
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: `Error: frame index ${frame} out of range (0-${totalFrames - 1})`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const buffer = await sharp(path, { page: frame }).png().toBuffer();
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "image",
|
|
52
|
+
data: buffer.toString("base64"),
|
|
53
|
+
mimeType: "image/png",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
server.tool("gif_frames_list", "List all frames of a GIF with their index, delay (ms), and FPS", { path: z.string().describe("Path to the GIF file") }, async ({ path }) => {
|
|
59
|
+
const metadata = await sharp(path, { animated: true }).metadata();
|
|
60
|
+
const frames = metadata.pages ?? 1;
|
|
61
|
+
const delays = metadata.delay ?? [];
|
|
62
|
+
const list = Array.from({ length: frames }, (_, i) => {
|
|
63
|
+
const delay = delays[i] ?? 100;
|
|
64
|
+
const fps = delay > 0 ? Math.round((1000 / delay) * 100) / 100 : 0;
|
|
65
|
+
return { index: i, delay, fps };
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify(list, null, 2),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
async function main() {
|
|
77
|
+
const transport = new StdioServerTransport();
|
|
78
|
+
await server.connect(transport);
|
|
79
|
+
}
|
|
80
|
+
main().catch((error) => {
|
|
81
|
+
console.error("Fatal error:", error);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-gif",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for GIF animation analysis — metadata, frame extraction, and frame listing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-gif": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"build"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"start": "node build/index.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"gif",
|
|
20
|
+
"animation",
|
|
21
|
+
"sharp",
|
|
22
|
+
"model-context-protocol"
|
|
23
|
+
],
|
|
24
|
+
"license": "BSD-2-Clause",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/signal-slot/mcp-gif.git"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
+
"sharp": "^0.33.5",
|
|
32
|
+
"zod": "^3.24.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.7.0"
|
|
37
|
+
}
|
|
38
|
+
}
|