@pdfgen/mcp 0.1.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 +95 -0
- package/dist/index.js +151 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# PDFgen MCP server
|
|
2
|
+
|
|
3
|
+
Let AI agents generate PDFs. This is a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
4
|
+
server that exposes the [PDFgen](https://pdfgen.com) API as tools, so Claude
|
|
5
|
+
Desktop (and any other MCP client) can turn HTML, Markdown, or a saved template
|
|
6
|
+
into a PDF and get back a hosted link.
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
| Tool | What it does |
|
|
11
|
+
|------|--------------|
|
|
12
|
+
| `generate_pdf` | Generate a PDF from `html`, `markdown`, or a saved `template_id` (with optional `data` for Handlebars tokens). Returns a hosted, expiring URL. |
|
|
13
|
+
| `list_templates` | List the saved templates on your PDFgen account. |
|
|
14
|
+
| `create_template` | Create a reusable template from HTML; returns its `template_id`. |
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- **Node.js 18 or newer** (the `npx` command and the server's `fetch` need it — check with `node -v`).
|
|
19
|
+
- An **MCP client** such as Claude Desktop.
|
|
20
|
+
- A **PDFgen API key** — sign in at [pdfgen.com](https://pdfgen.com) and copy a key from
|
|
21
|
+
[the Developer page](https://pdfgen.com/developer). It looks like `pdfg_live_...`.
|
|
22
|
+
|
|
23
|
+
## Setup (Claude Desktop)
|
|
24
|
+
|
|
25
|
+
Open your `claude_desktop_config.json` and add the `pdfgen` entry below. The file lives at:
|
|
26
|
+
|
|
27
|
+
- **macOS** — `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
28
|
+
- **Windows** — `%APPDATA%\Claude\claude_desktop_config.json`
|
|
29
|
+
- **Linux** — `~/.config/Claude/claude_desktop_config.json`
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"pdfgen": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["-y", "@pdfgen/mcp"],
|
|
37
|
+
"env": {
|
|
38
|
+
"PDFGEN_API_KEY": "pdfg_live_xxx"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Fully quit and reopen Claude Desktop** (not just close the window). PDFgen is
|
|
46
|
+
connected when the tools (🔨 icon) list `generate_pdf`, `list_templates`, and
|
|
47
|
+
`create_template`.
|
|
48
|
+
|
|
49
|
+
Then try:
|
|
50
|
+
|
|
51
|
+
> *"Generate an invoice PDF for Acme Corp for $2,400 and give me the link."*
|
|
52
|
+
|
|
53
|
+
A few more things to ask:
|
|
54
|
+
|
|
55
|
+
- *"Create a reusable certificate template, then generate one for Maria González."*
|
|
56
|
+
- *"List my PDFgen templates."*
|
|
57
|
+
|
|
58
|
+
## Any MCP client
|
|
59
|
+
|
|
60
|
+
The server speaks MCP over stdio. Run it with your key in the environment:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
PDFGEN_API_KEY=pdfg_live_xxx npx -y @pdfgen/mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Environment variables
|
|
67
|
+
|
|
68
|
+
| Variable | Required | Default | Notes |
|
|
69
|
+
|----------|----------|---------|-------|
|
|
70
|
+
| `PDFGEN_API_KEY` | ✅ | — | Your `pdfg_live_...` API key. |
|
|
71
|
+
| `PDFGEN_BASE_URL` | — | `https://pdfgen.com` | Override for self-hosted / staging. |
|
|
72
|
+
|
|
73
|
+
## Troubleshooting
|
|
74
|
+
|
|
75
|
+
| Symptom | Fix |
|
|
76
|
+
|---------|-----|
|
|
77
|
+
| Tools don't appear in Claude | Make sure you **fully quit** and reopened the app, and that the JSON is valid (a trailing comma or missing brace silently disables the server). |
|
|
78
|
+
| `missing PDFGEN_API_KEY` in logs | The `env.PDFGEN_API_KEY` value is empty or the key isn't set in your shell. Paste your real `pdfg_live_...` key. |
|
|
79
|
+
| `PDFgen API 401` | The key is wrong, revoked, or for a different environment. Copy a fresh one from the [Developer page](https://pdfgen.com/developer). |
|
|
80
|
+
| `npx: command not found` | Install Node.js 18+ (which ships `npx`) from [nodejs.org](https://nodejs.org). |
|
|
81
|
+
| Renders are watermarked | The free tier watermarks output — upgrade your PDFgen plan to remove it. |
|
|
82
|
+
|
|
83
|
+
## Local development
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install
|
|
87
|
+
npm run build # compile src → dist
|
|
88
|
+
PDFGEN_API_KEY=pdfg_live_xxx node dist/index.js
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`stdout` is the MCP protocol channel, so all logs go to `stderr`.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PDFgen MCP server (stdio).
|
|
4
|
+
*
|
|
5
|
+
* Exposes the PDFgen public API (POST /api/v1/...) as MCP tools so AI agents
|
|
6
|
+
* — Claude Desktop and any other MCP client — can generate PDFs and manage
|
|
7
|
+
* templates. Thin wrapper over the REST API; auth is a PDFgen API key.
|
|
8
|
+
*
|
|
9
|
+
* Env:
|
|
10
|
+
* PDFGEN_API_KEY (required) your pdfg_live_... key
|
|
11
|
+
* PDFGEN_BASE_URL (optional) defaults to https://pdfgen.com
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
const API_KEY = process.env.PDFGEN_API_KEY;
|
|
17
|
+
const BASE_URL = (process.env.PDFGEN_BASE_URL || "https://pdfgen.com").replace(/\/+$/, "");
|
|
18
|
+
if (!API_KEY) {
|
|
19
|
+
// Fail fast with a clear message on the stderr channel (stdout is the MCP transport).
|
|
20
|
+
console.error("PDFgen MCP: missing PDFGEN_API_KEY environment variable.");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
/** Call the PDFgen API with the configured key; returns parsed JSON or throws. */
|
|
24
|
+
async function api(path, init = {}) {
|
|
25
|
+
const { json, ...rest } = init;
|
|
26
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
27
|
+
...rest,
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
30
|
+
...(json !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
31
|
+
...(rest.headers || {}),
|
|
32
|
+
},
|
|
33
|
+
body: json !== undefined ? JSON.stringify(json) : rest.body,
|
|
34
|
+
});
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = text ? JSON.parse(text) : null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
parsed = text;
|
|
42
|
+
}
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const message = (parsed && typeof parsed === "object" && "error" in parsed
|
|
45
|
+
? JSON.stringify(parsed.error)
|
|
46
|
+
: typeof parsed === "string"
|
|
47
|
+
? parsed
|
|
48
|
+
: JSON.stringify(parsed)) || res.statusText;
|
|
49
|
+
throw new Error(`PDFgen API ${res.status}: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
/** Standard MCP text result. */
|
|
54
|
+
const text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
55
|
+
/** Standard MCP error result. */
|
|
56
|
+
const fail = (s) => ({ content: [{ type: "text", text: s }], isError: true });
|
|
57
|
+
const server = new McpServer({ name: "pdfgen", version: "0.1.0" });
|
|
58
|
+
server.registerTool("generate_pdf", {
|
|
59
|
+
title: "Generate a PDF",
|
|
60
|
+
description: "Generate a PDF from HTML, Markdown, or a saved PDFgen template, and return a hosted link to it. " +
|
|
61
|
+
"Provide exactly one of `html`, `markdown`, or `template_id`. Use `data` (a JSON object) to fill " +
|
|
62
|
+
"Handlebars tokens in the HTML or template. Returns an expiring URL to the rendered PDF.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
html: z.string().optional().describe("Raw HTML (optionally with {{handlebars}} tokens)."),
|
|
65
|
+
markdown: z.string().optional().describe("Markdown to render to a styled PDF."),
|
|
66
|
+
template_id: z.string().optional().describe("ID of a saved PDFgen template to render."),
|
|
67
|
+
data: z
|
|
68
|
+
.record(z.any())
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("JSON data object merged into Handlebars tokens."),
|
|
71
|
+
engine: z
|
|
72
|
+
.enum(["handlebars", "legacy"])
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('For raw html: "handlebars" (default) or "legacy" to skip templating.'),
|
|
75
|
+
format: z.string().optional().describe('Page format, e.g. "A4" or "Letter".'),
|
|
76
|
+
file_name: z.string().optional().describe("Optional file name for the generated PDF."),
|
|
77
|
+
},
|
|
78
|
+
}, async (args) => {
|
|
79
|
+
const sources = [args.html, args.markdown, args.template_id].filter(Boolean);
|
|
80
|
+
if (sources.length !== 1) {
|
|
81
|
+
return fail("Provide exactly one of `html`, `markdown`, or `template_id`.");
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const result = (await api("/api/v1/generate", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
json: {
|
|
87
|
+
html: args.html,
|
|
88
|
+
markdown: args.markdown,
|
|
89
|
+
template_id: args.template_id,
|
|
90
|
+
data: args.data,
|
|
91
|
+
engine: args.engine,
|
|
92
|
+
format: args.format,
|
|
93
|
+
file_name: args.file_name,
|
|
94
|
+
// Return a hosted link rather than a binary, which an MCP client can open.
|
|
95
|
+
export_type: "url",
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
if (!result?.url)
|
|
99
|
+
return fail(`Unexpected response: ${JSON.stringify(result)}`);
|
|
100
|
+
return text(`PDF generated: ${result.url}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
server.registerTool("list_templates", {
|
|
107
|
+
title: "List templates",
|
|
108
|
+
description: "List the saved PDFgen templates available to your account (id, name, engine).",
|
|
109
|
+
inputSchema: {},
|
|
110
|
+
}, async () => {
|
|
111
|
+
try {
|
|
112
|
+
const result = await api("/api/v1/templates", { method: "GET" });
|
|
113
|
+
return text(JSON.stringify(result, null, 2));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
server.registerTool("create_template", {
|
|
120
|
+
title: "Create a template",
|
|
121
|
+
description: "Create a reusable PDFgen template from HTML (with optional Handlebars tokens). " +
|
|
122
|
+
"Returns the new template's id, which you can then pass to generate_pdf as `template_id`.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
name: z.string().describe("A name for the template."),
|
|
125
|
+
content: z.string().describe("The template HTML (optionally with {{handlebars}} tokens)."),
|
|
126
|
+
engine: z
|
|
127
|
+
.enum(["handlebars", "legacy"])
|
|
128
|
+
.optional()
|
|
129
|
+
.describe('Templating engine: "handlebars" (default) or "legacy".'),
|
|
130
|
+
},
|
|
131
|
+
}, async (args) => {
|
|
132
|
+
try {
|
|
133
|
+
const result = await api("/api/v1/templates", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
json: { name: args.name, content: args.content, engine: args.engine },
|
|
136
|
+
});
|
|
137
|
+
return text(JSON.stringify(result, null, 2));
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
async function main() {
|
|
144
|
+
const transport = new StdioServerTransport();
|
|
145
|
+
await server.connect(transport);
|
|
146
|
+
console.error(`PDFgen MCP server running (base: ${BASE_URL}).`);
|
|
147
|
+
}
|
|
148
|
+
main().catch((err) => {
|
|
149
|
+
console.error("PDFgen MCP fatal error:", err);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pdfgen/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for PDFgen — let AI agents generate PDFs from HTML, Markdown, or saved templates.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": "PDFgen",
|
|
10
|
+
"homepage": "https://pdfgen.com/mcp",
|
|
11
|
+
"bin": {
|
|
12
|
+
"pdfgen-mcp": "dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"model-context-protocol",
|
|
30
|
+
"pdf",
|
|
31
|
+
"pdfgen",
|
|
32
|
+
"html-to-pdf",
|
|
33
|
+
"ai"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
|
+
"zod": "^3.25.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.6.0"
|
|
42
|
+
}
|
|
43
|
+
}
|