snapforge-mcp 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 +47 -0
- package/index.js +91 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# snapforge-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [SnapForge](https://snapforge.org) — gives AI agents two tools:
|
|
4
|
+
|
|
5
|
+
- **`snapforge_screenshot`** — capture a URL or HTML as PNG/JPEG (returned inline as an image)
|
|
6
|
+
- **`snapforge_pdf`** — render a URL or HTML to a PDF (saved to a temp file, path returned)
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
Get a free API key at <https://snapforge.org> (POST your email to `/signup`), then:
|
|
11
|
+
|
|
12
|
+
### Claude Desktop / Claude Code
|
|
13
|
+
|
|
14
|
+
Add to your MCP config (`claude_desktop_config.json` or `.mcp.json`):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"snapforge": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "snapforge-mcp"],
|
|
22
|
+
"env": { "SNAPFORGE_API_KEY": "sf_your_key_here" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Any MCP client
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
SNAPFORGE_API_KEY=sf_your_key npx -y snapforge-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Environment
|
|
35
|
+
|
|
36
|
+
- `SNAPFORGE_API_KEY` (required) — your SnapForge API key
|
|
37
|
+
- `SNAPFORGE_BASE_URL` (optional) — defaults to `https://snapforge.org`
|
|
38
|
+
|
|
39
|
+
## Tool parameters
|
|
40
|
+
|
|
41
|
+
Both tools accept `url` **or** `html`, plus `waitUntil` (`load`|`networkidle`) and `delay` (ms).
|
|
42
|
+
- `snapforge_screenshot`: `fullPage`, `width`, `height`, `scale`, `format` (`png`|`jpeg`), `quality`
|
|
43
|
+
- `snapforge_pdf`: `pageFormat` (`A4`, `Letter`, …), `landscape`, `printBackground`, `margin`
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
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 { z } from 'zod';
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const BASE = (process.env.SNAPFORGE_BASE_URL || 'https://snapforge.org').replace(/\/$/, '');
|
|
10
|
+
const API_KEY = process.env.SNAPFORGE_API_KEY || '';
|
|
11
|
+
|
|
12
|
+
// Call a SnapForge render endpoint and return the raw bytes + content type.
|
|
13
|
+
async function render(endpoint, body) {
|
|
14
|
+
if (!API_KEY) {
|
|
15
|
+
throw new Error('Set the SNAPFORGE_API_KEY environment variable (get one at https://snapforge.org).');
|
|
16
|
+
}
|
|
17
|
+
const res = await fetch(`${BASE}${endpoint}`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'x-api-key': API_KEY, 'content-type': 'application/json' },
|
|
20
|
+
body: JSON.stringify(body)
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
let detail = '';
|
|
24
|
+
try { detail = JSON.stringify(await res.json()); } catch { detail = await res.text().catch(() => ''); }
|
|
25
|
+
throw new Error(`SnapForge ${endpoint} failed (HTTP ${res.status}): ${detail}`);
|
|
26
|
+
}
|
|
27
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
28
|
+
return { buf, contentType: res.headers.get('content-type') || 'application/octet-stream' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const server = new McpServer({ name: 'snapforge', version: '1.0.0' });
|
|
32
|
+
|
|
33
|
+
const common = {
|
|
34
|
+
url: z.string().url().optional().describe('Public http/https URL to render'),
|
|
35
|
+
html: z.string().optional().describe('Raw HTML to render instead of a URL (max 2MB)'),
|
|
36
|
+
waitUntil: z.enum(['load', 'networkidle']).optional().describe('When to consider the page ready'),
|
|
37
|
+
delay: z.number().int().min(0).max(10000).optional().describe('Extra wait in ms before capture')
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
server.registerTool(
|
|
41
|
+
'snapforge_screenshot',
|
|
42
|
+
{
|
|
43
|
+
title: 'SnapForge screenshot',
|
|
44
|
+
description: 'Capture a screenshot of a public URL or raw HTML and return it as an image (PNG/JPEG).',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
...common,
|
|
47
|
+
fullPage: z.boolean().optional().describe('Capture the full scrollable page'),
|
|
48
|
+
width: z.number().int().min(100).max(3840).optional(),
|
|
49
|
+
height: z.number().int().min(100).max(2160).optional(),
|
|
50
|
+
scale: z.number().int().min(1).max(3).optional().describe('Device pixel ratio (high-DPI)'),
|
|
51
|
+
format: z.enum(['png', 'jpeg']).optional(),
|
|
52
|
+
quality: z.number().int().min(1).max(100).optional().describe('JPEG quality')
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async (args) => {
|
|
56
|
+
try {
|
|
57
|
+
const { buf, contentType } = await render('/v1/screenshot', args);
|
|
58
|
+
return { content: [{ type: 'image', data: buf.toString('base64'), mimeType: contentType }] };
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return { isError: true, content: [{ type: 'text', text: String(e.message || e) }] };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.registerTool(
|
|
66
|
+
'snapforge_pdf',
|
|
67
|
+
{
|
|
68
|
+
title: 'SnapForge PDF',
|
|
69
|
+
description: 'Render a public URL or raw HTML to a PDF. The PDF is written to a temp file and its path is returned.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
...common,
|
|
72
|
+
pageFormat: z.enum(['Letter', 'Legal', 'Tabloid', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6']).optional(),
|
|
73
|
+
landscape: z.boolean().optional(),
|
|
74
|
+
printBackground: z.boolean().optional(),
|
|
75
|
+
margin: z.string().optional().describe('CSS size for all margins, e.g. "1cm"')
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async (args) => {
|
|
79
|
+
try {
|
|
80
|
+
const { buf } = await render('/v1/pdf', args);
|
|
81
|
+
const file = path.join(tmpdir(), `snapforge-${Date.now()}.pdf`);
|
|
82
|
+
await writeFile(file, buf);
|
|
83
|
+
return { content: [{ type: 'text', text: `PDF saved to ${file} (${buf.length} bytes).` }] };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return { isError: true, content: [{ type: 'text', text: String(e.message || e) }] };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const transport = new StdioServerTransport();
|
|
91
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snapforge-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for SnapForge — screenshot & HTML-to-PDF API as agent tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "snapforge-mcp": "index.js" },
|
|
7
|
+
"files": ["index.js", "README.md"],
|
|
8
|
+
"keywords": ["mcp", "model-context-protocol", "screenshot", "pdf", "snapforge", "playwright"],
|
|
9
|
+
"homepage": "https://snapforge.org",
|
|
10
|
+
"repository": { "type": "git", "url": "git+https://github.com/sporty303/snapforge-mcp.git" },
|
|
11
|
+
"author": "Gerome Sportelli",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"engines": { "node": ">=18" },
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"zod": "^3.23.8"
|
|
17
|
+
}
|
|
18
|
+
}
|