pretext-pdf-mcp 1.0.2 → 1.0.3
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 +15 -1
- package/dist/index.js +50 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,21 @@ MCP server for [pretext-pdf](https://github.com/Himaan1998Y/pretext-pdf) — gen
|
|
|
4
4
|
|
|
5
5
|
No headless browser. No puppeteer. Pure Node.js with embedded fonts and precision text layout.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Connect via Smithery
|
|
8
|
+
|
|
9
|
+
The fastest way — no install, works instantly in any MCP-compatible agent:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
https://pretext-pdf.run.tools
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or add via CLI:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
smithery mcp add himaan4149-kv55/pretext-pdf
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Local Install (Claude Desktop / Cursor / Windsurf)
|
|
8
22
|
|
|
9
23
|
### Option 1: npx (no global install needed)
|
|
10
24
|
|
package/dist/index.js
CHANGED
|
@@ -24,29 +24,72 @@ function createServer() {
|
|
|
24
24
|
});
|
|
25
25
|
return server;
|
|
26
26
|
}
|
|
27
|
+
function setCorsHeaders(res) {
|
|
28
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
29
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
30
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
31
|
+
}
|
|
27
32
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
|
|
28
33
|
if (port) {
|
|
29
|
-
// HTTP mode — stateless, for hosted deployments (Smithery, VPS, etc.)
|
|
30
34
|
const { createServer: createHttpServer } = await import('node:http');
|
|
31
35
|
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
36
|
+
const { render } = await import('pretext-pdf');
|
|
32
37
|
const httpServer = createHttpServer(async (req, res) => {
|
|
33
38
|
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
39
|
+
setCorsHeaders(res);
|
|
40
|
+
// Preflight
|
|
41
|
+
if (req.method === 'OPTIONS') {
|
|
42
|
+
res.writeHead(204);
|
|
43
|
+
res.end();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Health check
|
|
34
47
|
if (url.pathname === '/health') {
|
|
35
48
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
36
49
|
res.end(JSON.stringify({ ok: true, service: 'pretext-pdf-mcp' }));
|
|
37
50
|
return;
|
|
38
51
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
// REST demo API — POST /api/generate → returns PDF bytes
|
|
53
|
+
if (url.pathname === '/api/generate' && req.method === 'POST') {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
for await (const chunk of req)
|
|
56
|
+
chunks.push(chunk);
|
|
57
|
+
if (Buffer.concat(chunks).length > 100_000) {
|
|
58
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
59
|
+
res.end(JSON.stringify({ error: 'Request too large' }));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let body;
|
|
63
|
+
try {
|
|
64
|
+
body = JSON.parse(Buffer.concat(chunks).toString());
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
68
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const pdf = await render(body.data);
|
|
73
|
+
res.writeHead(200, {
|
|
74
|
+
'Content-Type': 'application/pdf',
|
|
75
|
+
'Content-Disposition': 'inline; filename="output.pdf"',
|
|
76
|
+
'Content-Length': pdf.byteLength,
|
|
77
|
+
});
|
|
78
|
+
res.end(Buffer.from(pdf));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
82
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
83
|
+
res.end(JSON.stringify({ error: msg }));
|
|
84
|
+
}
|
|
42
85
|
return;
|
|
43
86
|
}
|
|
44
|
-
|
|
87
|
+
// MCP endpoint — POST /mcp (stateless)
|
|
88
|
+
if (url.pathname === '/mcp' && req.method === 'POST') {
|
|
45
89
|
const chunks = [];
|
|
46
90
|
for await (const chunk of req)
|
|
47
91
|
chunks.push(chunk);
|
|
48
92
|
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
49
|
-
// Stateless: fresh transport+server per request — no session tracking
|
|
50
93
|
const transport = new StreamableHTTPServerTransport({
|
|
51
94
|
sessionIdGenerator: undefined,
|
|
52
95
|
});
|
|
@@ -55,7 +98,7 @@ if (port) {
|
|
|
55
98
|
await transport.handleRequest(req, res, body);
|
|
56
99
|
return;
|
|
57
100
|
}
|
|
58
|
-
res.writeHead(
|
|
101
|
+
res.writeHead(404);
|
|
59
102
|
res.end();
|
|
60
103
|
});
|
|
61
104
|
httpServer.listen(port, () => {
|