pretext-pdf-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 +187 -0
- package/dist/index.js +25 -0
- package/dist/tools/generate-invoice.js +323 -0
- package/dist/tools/generate-pdf.js +59 -0
- package/dist/tools/generate-report.js +253 -0
- package/dist/tools/list-elements.js +98 -0
- package/dist/utils/base64.js +6 -0
- package/package.json +29 -0
- package/smithery.yaml +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# pretext-pdf-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [pretext-pdf](https://github.com/Himaan1998Y/pretext-pdf) — generate professional PDFs from structured JSON in Claude, Cursor, or any AI agent.
|
|
4
|
+
|
|
5
|
+
No headless browser. No puppeteer. Pure Node.js with embedded fonts and precision text layout.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### Option 1: npx (no global install needed)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx pretext-pdf-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Option 2: Global install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g pretext-pdf-mcp
|
|
19
|
+
pretext-pdf-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Claude Desktop Configuration
|
|
23
|
+
|
|
24
|
+
Add to your `claude_desktop_config.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"pretext-pdf": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "pretext-pdf-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Config file location:
|
|
38
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
39
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
| Tool | Input | Output |
|
|
44
|
+
|------|-------|--------|
|
|
45
|
+
| `generate_pdf` | PdfDocument JSON descriptor | Base64 PDF + filename + size |
|
|
46
|
+
| `generate_invoice` | Invoice data (parties, items, GST, currency) | Base64 PDF |
|
|
47
|
+
| `generate_report` | Report sections with optional tables and callouts | Base64 PDF |
|
|
48
|
+
| `list_element_types` | none | Markdown reference of all element types |
|
|
49
|
+
|
|
50
|
+
### generate_pdf
|
|
51
|
+
|
|
52
|
+
Full-power access to the pretext-pdf API. Pass any PdfDocument descriptor.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"document": {
|
|
57
|
+
"pageSize": "A4",
|
|
58
|
+
"footer": { "text": "Page {{pageNumber}} of {{totalPages}}", "fontSize": 9 },
|
|
59
|
+
"content": [
|
|
60
|
+
{ "type": "heading", "level": 1, "text": "My Document" },
|
|
61
|
+
{ "type": "paragraph", "text": "Hello world." }
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"filename": "my-document"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"success": true,
|
|
72
|
+
"base64": "<base64-encoded PDF bytes>",
|
|
73
|
+
"filename": "my-document.pdf",
|
|
74
|
+
"size_bytes": 42816
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### generate_invoice
|
|
79
|
+
|
|
80
|
+
Business-friendly invoice generator. No PDF knowledge needed.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"from": {
|
|
85
|
+
"company": "Antigravity Systems",
|
|
86
|
+
"address": "Gurugram, Haryana",
|
|
87
|
+
"gstin": "06AABCA1234Z1ZK",
|
|
88
|
+
"email": "hello@antigravity.dev"
|
|
89
|
+
},
|
|
90
|
+
"to": {
|
|
91
|
+
"company": "TCS Ltd",
|
|
92
|
+
"address": "Mumbai, Maharashtra",
|
|
93
|
+
"gstin": "27AAACT2727Q1ZW"
|
|
94
|
+
},
|
|
95
|
+
"invoice_number": "INV-2026-001",
|
|
96
|
+
"date": "2026-04-08",
|
|
97
|
+
"due_date": "2026-05-08",
|
|
98
|
+
"currency": "INR",
|
|
99
|
+
"items": [
|
|
100
|
+
{
|
|
101
|
+
"description": "LLM Fine-tuning Pipeline",
|
|
102
|
+
"hsn_code": "998314",
|
|
103
|
+
"quantity": 1,
|
|
104
|
+
"rate": 250000,
|
|
105
|
+
"gst_rate": 18
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"description": "AI Strategy Workshop",
|
|
109
|
+
"quantity": 2,
|
|
110
|
+
"rate": 75000,
|
|
111
|
+
"gst_rate": 18
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"notes": "Payment due within 30 days. NEFT/IMPS preferred."
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"success": true,
|
|
122
|
+
"base64": "<base64-encoded PDF bytes>",
|
|
123
|
+
"filename": "invoice-INV-2026-001.pdf",
|
|
124
|
+
"size_bytes": 68420
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Features:
|
|
129
|
+
- Supports INR, USD, EUR, GBP
|
|
130
|
+
- Auto-calculates IGST per line item when `gst_rate` is set
|
|
131
|
+
- HSN/SAC code column appears automatically when any item has it
|
|
132
|
+
- Company header with from/to details in a 2-column table
|
|
133
|
+
- Professional footer with invoice number and page numbers
|
|
134
|
+
|
|
135
|
+
### generate_report
|
|
136
|
+
|
|
137
|
+
Multi-section report with optional TOC, tables, and callout boxes.
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"title": "Haryana Real Estate Q1 2026",
|
|
142
|
+
"subtitle": "Residential & Commercial Analysis",
|
|
143
|
+
"author": "Antigravity Research",
|
|
144
|
+
"include_toc": true,
|
|
145
|
+
"sections": [
|
|
146
|
+
{
|
|
147
|
+
"heading": "Executive Summary",
|
|
148
|
+
"body": "Strong growth across all micro-markets.\n\nGurugram led with 18% YoY volume growth.",
|
|
149
|
+
"table": {
|
|
150
|
+
"headers": ["Market", "Avg Rs./sqft", "YoY"],
|
|
151
|
+
"rows": [
|
|
152
|
+
["New Gurugram", "9,800", "+15.6%"],
|
|
153
|
+
["Sohna Road", "8,400", "+12.1%"]
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"callout": {
|
|
157
|
+
"style": "warning",
|
|
158
|
+
"text": "Repo rate risk: any hike above 6.75% could suppress volumes 10-15%."
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### list_element_types
|
|
166
|
+
|
|
167
|
+
No input. Returns a markdown reference of all 16 element types (paragraph, heading, table, list, image, svg, code, blockquote, callout, toc, form-field, comment, hr, spacer, page-break, rich-paragraph) with key properties and examples.
|
|
168
|
+
|
|
169
|
+
## Decoding the base64 PDF
|
|
170
|
+
|
|
171
|
+
In Node.js:
|
|
172
|
+
```javascript
|
|
173
|
+
const bytes = Buffer.from(result.base64, 'base64')
|
|
174
|
+
fs.writeFileSync('output.pdf', bytes)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
In Python:
|
|
178
|
+
```python
|
|
179
|
+
import base64
|
|
180
|
+
pdf_bytes = base64.b64decode(result['base64'])
|
|
181
|
+
with open('output.pdf', 'wb') as f:
|
|
182
|
+
f.write(pdf_bytes)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT — Himanshu Jain
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { generatePdfTool } from './tools/generate-pdf.js';
|
|
6
|
+
import { generateInvoiceTool } from './tools/generate-invoice.js';
|
|
7
|
+
import { generateReportTool } from './tools/generate-report.js';
|
|
8
|
+
import { listElementsTool } from './tools/list-elements.js';
|
|
9
|
+
const server = new Server({ name: 'pretext-pdf', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
10
|
+
const tools = [generatePdfTool, generateInvoiceTool, generateReportTool, listElementsTool];
|
|
11
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
12
|
+
tools: tools.map(t => t.schema),
|
|
13
|
+
}));
|
|
14
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
15
|
+
const tool = tools.find(t => t.schema.name === request.params.name);
|
|
16
|
+
if (!tool) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return tool.handler(request.params.arguments ?? {});
|
|
23
|
+
});
|
|
24
|
+
const transport = new StdioServerTransport();
|
|
25
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { render } from 'pretext-pdf';
|
|
2
|
+
import { toBase64 } from '../utils/base64.js';
|
|
3
|
+
const CURRENCY_SYMBOLS = {
|
|
4
|
+
INR: '₹',
|
|
5
|
+
USD: '$',
|
|
6
|
+
EUR: '€',
|
|
7
|
+
GBP: '£',
|
|
8
|
+
};
|
|
9
|
+
function formatMoney(amount, symbol) {
|
|
10
|
+
return `${symbol}${amount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
11
|
+
}
|
|
12
|
+
function partyBlock(p) {
|
|
13
|
+
const lines = [p.company];
|
|
14
|
+
if (p.address)
|
|
15
|
+
lines.push(p.address);
|
|
16
|
+
if (p.gstin)
|
|
17
|
+
lines.push(`GSTIN: ${p.gstin}`);
|
|
18
|
+
if (p.email)
|
|
19
|
+
lines.push(p.email);
|
|
20
|
+
if (p.phone)
|
|
21
|
+
lines.push(p.phone);
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
24
|
+
function todayISO() {
|
|
25
|
+
return new Date().toISOString().slice(0, 10);
|
|
26
|
+
}
|
|
27
|
+
function buildInvoiceDocument(input) {
|
|
28
|
+
const currency = input.currency ?? 'INR';
|
|
29
|
+
const sym = CURRENCY_SYMBOLS[currency];
|
|
30
|
+
const date = input.date ?? todayISO();
|
|
31
|
+
const invoiceNo = input.invoice_number ?? `INV-${Date.now()}`;
|
|
32
|
+
const hasHsn = input.items.some(i => i.hsn_code);
|
|
33
|
+
const hasGst = input.items.some(i => i.gst_rate !== undefined && i.gst_rate > 0);
|
|
34
|
+
// Build columns for line items table
|
|
35
|
+
const itemColumns = [{ width: '3*', align: 'left' }];
|
|
36
|
+
if (hasHsn)
|
|
37
|
+
itemColumns.push({ width: 70, align: 'center' });
|
|
38
|
+
itemColumns.push({ width: 60, align: 'right' });
|
|
39
|
+
itemColumns.push({ width: 80, align: 'right' });
|
|
40
|
+
itemColumns.push({ width: 90, align: 'right' });
|
|
41
|
+
// Header row for items table
|
|
42
|
+
const headerCells = [
|
|
43
|
+
{ text: 'Description', fontWeight: 700, color: '#ffffff' },
|
|
44
|
+
];
|
|
45
|
+
if (hasHsn)
|
|
46
|
+
headerCells.push({ text: 'HSN', fontWeight: 700, color: '#ffffff' });
|
|
47
|
+
headerCells.push({ text: 'Qty', fontWeight: 700, color: '#ffffff' });
|
|
48
|
+
headerCells.push({ text: 'Rate', fontWeight: 700, color: '#ffffff' });
|
|
49
|
+
headerCells.push({ text: 'Amount', fontWeight: 700, color: '#ffffff' });
|
|
50
|
+
// Data rows
|
|
51
|
+
let subtotal = 0;
|
|
52
|
+
const itemRows = [{ isHeader: true, cells: headerCells }];
|
|
53
|
+
for (const item of input.items) {
|
|
54
|
+
const amount = item.quantity * item.rate;
|
|
55
|
+
subtotal += amount;
|
|
56
|
+
const cells = [{ text: item.description }];
|
|
57
|
+
if (hasHsn)
|
|
58
|
+
cells.push({ text: item.hsn_code ?? '' });
|
|
59
|
+
cells.push({ text: String(item.quantity) });
|
|
60
|
+
cells.push({ text: formatMoney(item.rate, sym) });
|
|
61
|
+
cells.push({ text: formatMoney(amount, sym) });
|
|
62
|
+
itemRows.push({ cells });
|
|
63
|
+
}
|
|
64
|
+
// GST calculation: use IGST if inter-state (default), or CGST+SGST if intra
|
|
65
|
+
// We'll use IGST for simplicity (single tax line)
|
|
66
|
+
const totalGst = hasGst
|
|
67
|
+
? input.items.reduce((sum, item) => {
|
|
68
|
+
const amount = item.quantity * item.rate;
|
|
69
|
+
const rate = item.gst_rate ?? 0;
|
|
70
|
+
return sum + (amount * rate) / 100;
|
|
71
|
+
}, 0)
|
|
72
|
+
: 0;
|
|
73
|
+
const grandTotal = subtotal + totalGst;
|
|
74
|
+
// Build totals section
|
|
75
|
+
const totalsContent = [
|
|
76
|
+
{ type: 'hr', color: '#dddddd', thickness: 0.5, spaceBelow: 6 },
|
|
77
|
+
{
|
|
78
|
+
type: 'paragraph',
|
|
79
|
+
text: `Subtotal: ${formatMoney(subtotal, sym)}`,
|
|
80
|
+
align: 'right',
|
|
81
|
+
spaceAfter: hasGst ? 4 : 8,
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
if (hasGst) {
|
|
85
|
+
// Build per-rate GST breakdown
|
|
86
|
+
const rateGroups = {};
|
|
87
|
+
for (const item of input.items) {
|
|
88
|
+
if (!item.gst_rate)
|
|
89
|
+
continue;
|
|
90
|
+
const amount = item.quantity * item.rate;
|
|
91
|
+
rateGroups[item.gst_rate] = (rateGroups[item.gst_rate] ?? 0) + (amount * item.gst_rate) / 100;
|
|
92
|
+
}
|
|
93
|
+
for (const [rate, gstAmt] of Object.entries(rateGroups)) {
|
|
94
|
+
totalsContent.push({
|
|
95
|
+
type: 'paragraph',
|
|
96
|
+
text: `IGST @ ${rate}%: ${formatMoney(gstAmt, sym)}`,
|
|
97
|
+
align: 'right',
|
|
98
|
+
color: '#555555',
|
|
99
|
+
spaceAfter: 4,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
totalsContent.push({ type: 'hr', color: '#1a1a2e', thickness: 1, spaceBelow: 6 });
|
|
103
|
+
totalsContent.push({
|
|
104
|
+
type: 'paragraph',
|
|
105
|
+
text: `GRAND TOTAL: ${formatMoney(grandTotal, sym)}`,
|
|
106
|
+
fontSize: 13,
|
|
107
|
+
fontWeight: 700,
|
|
108
|
+
color: '#1a1a2e',
|
|
109
|
+
align: 'right',
|
|
110
|
+
spaceAfter: 16,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
totalsContent.push({ type: 'hr', color: '#1a1a2e', thickness: 1, spaceBelow: 6 });
|
|
115
|
+
totalsContent.push({
|
|
116
|
+
type: 'paragraph',
|
|
117
|
+
text: `TOTAL: ${formatMoney(grandTotal, sym)}`,
|
|
118
|
+
fontSize: 13,
|
|
119
|
+
fontWeight: 700,
|
|
120
|
+
color: '#1a1a2e',
|
|
121
|
+
align: 'right',
|
|
122
|
+
spaceAfter: 16,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const content = [
|
|
126
|
+
// Header: company name
|
|
127
|
+
{ type: 'heading', level: 1, text: input.from.company, fontSize: 22, color: '#1a1a2e', spaceAfter: 4 },
|
|
128
|
+
{
|
|
129
|
+
type: 'paragraph',
|
|
130
|
+
text: [input.from.address, input.from.gstin ? `GSTIN: ${input.from.gstin}` : null]
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.join(' · '),
|
|
133
|
+
fontSize: 9,
|
|
134
|
+
color: '#666666',
|
|
135
|
+
spaceAfter: 2,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'paragraph',
|
|
139
|
+
text: [input.from.email, input.from.phone].filter(Boolean).join(' · '),
|
|
140
|
+
fontSize: 9,
|
|
141
|
+
color: '#0070f3',
|
|
142
|
+
spaceAfter: 14,
|
|
143
|
+
},
|
|
144
|
+
{ type: 'hr', color: '#1a1a2e', thickness: 2, spaceBelow: 12 },
|
|
145
|
+
// Invoice meta
|
|
146
|
+
{ type: 'heading', level: 3, text: 'INVOICE', fontSize: 16, color: '#1a1a2e', spaceAfter: 8 },
|
|
147
|
+
{
|
|
148
|
+
type: 'table',
|
|
149
|
+
columns: [{ width: '1*' }, { width: '1*' }],
|
|
150
|
+
rows: [
|
|
151
|
+
{
|
|
152
|
+
cells: [
|
|
153
|
+
{ text: `Invoice No.\n${invoiceNo}` },
|
|
154
|
+
{ text: `Bill To\n${partyBlock(input.to)}` },
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
cells: [
|
|
159
|
+
{ text: `Date\n${date}` },
|
|
160
|
+
{ text: input.due_date ? `Due Date\n${input.due_date}` : '' },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
borderColor: '#e8e8e8',
|
|
165
|
+
borderWidth: 0.5,
|
|
166
|
+
cellPaddingH: 10,
|
|
167
|
+
cellPaddingV: 8,
|
|
168
|
+
spaceAfter: 16,
|
|
169
|
+
},
|
|
170
|
+
// Line items
|
|
171
|
+
{ type: 'heading', level: 3, text: 'Services / Items', color: '#1a1a2e', spaceAfter: 6 },
|
|
172
|
+
{
|
|
173
|
+
type: 'table',
|
|
174
|
+
columns: itemColumns,
|
|
175
|
+
rows: itemRows,
|
|
176
|
+
headerBgColor: '#1a1a2e',
|
|
177
|
+
borderColor: '#e0e0e0',
|
|
178
|
+
borderWidth: 0.5,
|
|
179
|
+
cellPaddingH: 8,
|
|
180
|
+
cellPaddingV: 6,
|
|
181
|
+
spaceAfter: 4,
|
|
182
|
+
},
|
|
183
|
+
// Totals
|
|
184
|
+
...totalsContent,
|
|
185
|
+
];
|
|
186
|
+
// Notes
|
|
187
|
+
if (input.notes) {
|
|
188
|
+
content.push({ type: 'hr', color: '#e8e8e8', thickness: 0.5, spaceBelow: 10 });
|
|
189
|
+
content.push({ type: 'heading', level: 4, text: 'Notes', spaceAfter: 4 });
|
|
190
|
+
content.push({ type: 'paragraph', text: input.notes, fontSize: 10, color: '#555555', spaceAfter: 12 });
|
|
191
|
+
}
|
|
192
|
+
// Footer note
|
|
193
|
+
content.push({ type: 'hr', color: '#e8e8e8', thickness: 0.5, spaceBelow: 8 });
|
|
194
|
+
content.push({
|
|
195
|
+
type: 'paragraph',
|
|
196
|
+
text: 'Generated by pretext-pdf',
|
|
197
|
+
fontSize: 8,
|
|
198
|
+
color: '#aaaaaa',
|
|
199
|
+
align: 'center',
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
pageSize: 'A4',
|
|
203
|
+
margins: { top: 50, bottom: 50, left: 56, right: 56 },
|
|
204
|
+
defaultFontSize: 10,
|
|
205
|
+
footer: {
|
|
206
|
+
text: `Invoice ${invoiceNo} · Page {{pageNumber}} of {{totalPages}}`,
|
|
207
|
+
fontSize: 8,
|
|
208
|
+
color: '#aaaaaa',
|
|
209
|
+
align: 'center',
|
|
210
|
+
},
|
|
211
|
+
content,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export const generateInvoiceTool = {
|
|
215
|
+
schema: {
|
|
216
|
+
name: 'generate_invoice',
|
|
217
|
+
description: 'Generate a professional invoice PDF. Accepts structured invoice data (from/to parties, line items, GST). Returns base64-encoded PDF. Supports INR/USD/EUR/GBP currencies. GST (IGST) is auto-calculated when gst_rate is set on items.',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
from: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
description: 'Issuing party (your company)',
|
|
224
|
+
properties: {
|
|
225
|
+
company: { type: 'string' },
|
|
226
|
+
address: { type: 'string' },
|
|
227
|
+
gstin: { type: 'string' },
|
|
228
|
+
email: { type: 'string' },
|
|
229
|
+
phone: { type: 'string' },
|
|
230
|
+
},
|
|
231
|
+
required: ['company'],
|
|
232
|
+
},
|
|
233
|
+
to: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
description: 'Billing party (client)',
|
|
236
|
+
properties: {
|
|
237
|
+
company: { type: 'string' },
|
|
238
|
+
address: { type: 'string' },
|
|
239
|
+
gstin: { type: 'string' },
|
|
240
|
+
},
|
|
241
|
+
required: ['company'],
|
|
242
|
+
},
|
|
243
|
+
invoice_number: { type: 'string', description: 'Invoice identifier e.g. INV-2026-001' },
|
|
244
|
+
date: { type: 'string', description: 'Invoice date ISO format YYYY-MM-DD. Defaults to today.' },
|
|
245
|
+
due_date: { type: 'string', description: 'Payment due date ISO format.' },
|
|
246
|
+
currency: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
enum: ['INR', 'USD', 'EUR', 'GBP'],
|
|
249
|
+
description: 'Currency. Default: INR',
|
|
250
|
+
},
|
|
251
|
+
items: {
|
|
252
|
+
type: 'array',
|
|
253
|
+
description: 'Line items',
|
|
254
|
+
items: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
properties: {
|
|
257
|
+
description: { type: 'string' },
|
|
258
|
+
hsn_code: { type: 'string', description: 'HSN/SAC code for India GST' },
|
|
259
|
+
quantity: { type: 'number' },
|
|
260
|
+
rate: { type: 'number', description: 'Unit price' },
|
|
261
|
+
gst_rate: {
|
|
262
|
+
type: 'number',
|
|
263
|
+
enum: [0, 5, 12, 18, 28],
|
|
264
|
+
description: 'GST rate %. If set, IGST is calculated.',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
required: ['description', 'quantity', 'rate'],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
notes: { type: 'string', description: 'Additional notes or payment terms.' },
|
|
271
|
+
filename: { type: 'string', description: 'Suggested filename without .pdf extension.' },
|
|
272
|
+
},
|
|
273
|
+
required: ['from', 'to', 'items'],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
handler: async (args) => {
|
|
277
|
+
try {
|
|
278
|
+
// Validate required fields
|
|
279
|
+
if (!args.from || !args.to) {
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'VALIDATION_ERROR', message: 'from and to are required' }) }],
|
|
282
|
+
isError: true,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const items = args.items;
|
|
286
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'VALIDATION_ERROR', message: 'items must be a non-empty array' }) }],
|
|
289
|
+
isError: true,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const input = args;
|
|
293
|
+
const doc = buildInvoiceDocument(input);
|
|
294
|
+
const bytes = await render(doc);
|
|
295
|
+
const base64 = toBase64(bytes);
|
|
296
|
+
const invoiceNo = input.invoice_number ?? 'invoice';
|
|
297
|
+
const filename = (args.filename ?? `invoice-${invoiceNo}`) + '.pdf';
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: 'text',
|
|
302
|
+
text: JSON.stringify({ success: true, base64, filename, size_bytes: bytes.length }),
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: 'text',
|
|
312
|
+
text: JSON.stringify({
|
|
313
|
+
success: false,
|
|
314
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
315
|
+
message: err.message,
|
|
316
|
+
}),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
isError: true,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { render } from 'pretext-pdf';
|
|
2
|
+
import { toBase64 } from '../utils/base64.js';
|
|
3
|
+
export const generatePdfTool = {
|
|
4
|
+
schema: {
|
|
5
|
+
name: 'generate_pdf',
|
|
6
|
+
description: 'Generate a PDF from a pretext-pdf document descriptor (PdfDocument JSON). Returns base64-encoded PDF bytes. Use list_element_types to see available elements.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
document: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
description: 'A PdfDocument config object with content array and optional pageSize, margins, fonts, header, footer, watermark, encryption, etc.',
|
|
13
|
+
},
|
|
14
|
+
filename: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Suggested filename (without .pdf extension)',
|
|
17
|
+
default: 'document',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ['document'],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
handler: async (args) => {
|
|
24
|
+
try {
|
|
25
|
+
if (!args.document || typeof args.document !== 'object') {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: 'text', text: 'Error: document is required and must be an object' }],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const bytes = await render(args.document);
|
|
32
|
+
const base64 = toBase64(bytes);
|
|
33
|
+
const filename = (args.filename ?? 'document') + '.pdf';
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: JSON.stringify({ success: true, base64, filename, size_bytes: bytes.length }),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
success: false,
|
|
50
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
51
|
+
message: err.message,
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { render } from 'pretext-pdf';
|
|
2
|
+
import { toBase64 } from '../utils/base64.js';
|
|
3
|
+
const CALLOUT_COLORS = {
|
|
4
|
+
info: '#0070f3',
|
|
5
|
+
warning: '#f59e0b',
|
|
6
|
+
tip: '#10b981',
|
|
7
|
+
note: '#6366f1',
|
|
8
|
+
};
|
|
9
|
+
function todayISO() {
|
|
10
|
+
return new Date().toISOString().slice(0, 10);
|
|
11
|
+
}
|
|
12
|
+
function buildReportDocument(input) {
|
|
13
|
+
const includeToc = input.include_toc !== false;
|
|
14
|
+
const date = input.date ?? todayISO();
|
|
15
|
+
const content = [
|
|
16
|
+
// Cover block
|
|
17
|
+
{ type: 'spacer', height: 40 },
|
|
18
|
+
{
|
|
19
|
+
type: 'heading',
|
|
20
|
+
level: 1,
|
|
21
|
+
text: input.title,
|
|
22
|
+
fontSize: 28,
|
|
23
|
+
color: '#1a1a2e',
|
|
24
|
+
align: 'center',
|
|
25
|
+
spaceAfter: 10,
|
|
26
|
+
bookmark: false,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
if (input.subtitle) {
|
|
30
|
+
content.push({
|
|
31
|
+
type: 'paragraph',
|
|
32
|
+
text: input.subtitle,
|
|
33
|
+
fontSize: 14,
|
|
34
|
+
color: '#555555',
|
|
35
|
+
align: 'center',
|
|
36
|
+
spaceAfter: 10,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const metaParts = [];
|
|
40
|
+
if (input.author)
|
|
41
|
+
metaParts.push(input.author);
|
|
42
|
+
metaParts.push(date);
|
|
43
|
+
content.push({
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
text: metaParts.join(' · '),
|
|
46
|
+
fontSize: 10,
|
|
47
|
+
color: '#888888',
|
|
48
|
+
align: 'center',
|
|
49
|
+
spaceAfter: 6,
|
|
50
|
+
});
|
|
51
|
+
content.push({ type: 'hr', color: '#1a1a2e', thickness: 2, spaceBelow: 40 });
|
|
52
|
+
// TOC
|
|
53
|
+
if (includeToc) {
|
|
54
|
+
content.push({
|
|
55
|
+
type: 'toc',
|
|
56
|
+
title: 'Contents',
|
|
57
|
+
showTitle: true,
|
|
58
|
+
leader: '.',
|
|
59
|
+
minLevel: 1,
|
|
60
|
+
maxLevel: 2,
|
|
61
|
+
fontSize: 11,
|
|
62
|
+
spaceAfter: 24,
|
|
63
|
+
});
|
|
64
|
+
content.push({ type: 'page-break' });
|
|
65
|
+
}
|
|
66
|
+
// Sections
|
|
67
|
+
for (const section of input.sections) {
|
|
68
|
+
content.push({
|
|
69
|
+
type: 'heading',
|
|
70
|
+
level: 1,
|
|
71
|
+
text: section.heading,
|
|
72
|
+
anchor: section.heading.toLowerCase().replace(/\s+/g, '-'),
|
|
73
|
+
spaceAfter: 8,
|
|
74
|
+
});
|
|
75
|
+
// Body: split on double newlines for multiple paragraphs, single newlines become spaces
|
|
76
|
+
const paragraphs = section.body.split(/\n\n+/);
|
|
77
|
+
for (const para of paragraphs) {
|
|
78
|
+
if (para.trim()) {
|
|
79
|
+
content.push({
|
|
80
|
+
type: 'paragraph',
|
|
81
|
+
text: para.trim().replace(/\n/g, ' '),
|
|
82
|
+
spaceAfter: 8,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (section.table) {
|
|
87
|
+
const { headers, rows } = section.table;
|
|
88
|
+
const columns = headers.map(() => ({ width: `${Math.floor(100 / headers.length)}%` }));
|
|
89
|
+
// Use equal fractional widths
|
|
90
|
+
const fracColumns = headers.map(() => ({ width: '1*', align: 'left' }));
|
|
91
|
+
const headerRow = {
|
|
92
|
+
isHeader: true,
|
|
93
|
+
cells: headers.map(h => ({ text: h, fontWeight: 700, color: '#ffffff' })),
|
|
94
|
+
};
|
|
95
|
+
const dataRows = rows.map(row => ({
|
|
96
|
+
cells: row.map(cell => ({ text: cell })),
|
|
97
|
+
}));
|
|
98
|
+
content.push({
|
|
99
|
+
type: 'table',
|
|
100
|
+
columns: fracColumns,
|
|
101
|
+
rows: [headerRow, ...dataRows],
|
|
102
|
+
headerBgColor: '#1a1a2e',
|
|
103
|
+
borderColor: '#dddddd',
|
|
104
|
+
borderWidth: 0.5,
|
|
105
|
+
cellPaddingH: 8,
|
|
106
|
+
cellPaddingV: 6,
|
|
107
|
+
spaceAfter: 12,
|
|
108
|
+
});
|
|
109
|
+
// Suppress unused variable warning
|
|
110
|
+
void columns;
|
|
111
|
+
}
|
|
112
|
+
if (section.callout) {
|
|
113
|
+
const borderColor = CALLOUT_COLORS[section.callout.style] ?? '#888888';
|
|
114
|
+
content.push({
|
|
115
|
+
type: 'callout',
|
|
116
|
+
style: section.callout.style,
|
|
117
|
+
content: section.callout.text,
|
|
118
|
+
borderColor,
|
|
119
|
+
spaceAfter: 12,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
pageSize: 'A4',
|
|
125
|
+
margins: { top: 60, bottom: 60, left: 64, right: 64 },
|
|
126
|
+
defaultFontSize: 11,
|
|
127
|
+
bookmarks: { minLevel: 1, maxLevel: 3 },
|
|
128
|
+
header: {
|
|
129
|
+
text: input.title,
|
|
130
|
+
fontSize: 8,
|
|
131
|
+
color: '#999999',
|
|
132
|
+
align: 'right',
|
|
133
|
+
},
|
|
134
|
+
footer: {
|
|
135
|
+
text: 'Page {{pageNumber}} of {{totalPages}}',
|
|
136
|
+
fontSize: 8,
|
|
137
|
+
color: '#999999',
|
|
138
|
+
align: 'center',
|
|
139
|
+
},
|
|
140
|
+
metadata: {
|
|
141
|
+
title: input.title,
|
|
142
|
+
author: input.author,
|
|
143
|
+
subject: input.subtitle,
|
|
144
|
+
},
|
|
145
|
+
content,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export const generateReportTool = {
|
|
149
|
+
schema: {
|
|
150
|
+
name: 'generate_report',
|
|
151
|
+
description: 'Generate a professional multi-section report PDF with optional TOC, tables, and callout boxes. Returns base64-encoded PDF.',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
title: { type: 'string', description: 'Report title' },
|
|
156
|
+
subtitle: { type: 'string', description: 'Report subtitle or description' },
|
|
157
|
+
author: { type: 'string', description: 'Author name' },
|
|
158
|
+
date: { type: 'string', description: 'Date string. Defaults to today.' },
|
|
159
|
+
include_toc: {
|
|
160
|
+
type: 'boolean',
|
|
161
|
+
description: 'Include a Table of Contents page. Default: true',
|
|
162
|
+
default: true,
|
|
163
|
+
},
|
|
164
|
+
sections: {
|
|
165
|
+
type: 'array',
|
|
166
|
+
description: 'Report sections',
|
|
167
|
+
items: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {
|
|
170
|
+
heading: { type: 'string', description: 'Section heading' },
|
|
171
|
+
body: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
description: 'Section body text. Use double newlines (\\n\\n) to separate paragraphs.',
|
|
174
|
+
},
|
|
175
|
+
table: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
description: 'Optional data table',
|
|
178
|
+
properties: {
|
|
179
|
+
headers: { type: 'array', items: { type: 'string' } },
|
|
180
|
+
rows: {
|
|
181
|
+
type: 'array',
|
|
182
|
+
items: { type: 'array', items: { type: 'string' } },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: ['headers', 'rows'],
|
|
186
|
+
},
|
|
187
|
+
callout: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
description: 'Optional callout / alert box',
|
|
190
|
+
properties: {
|
|
191
|
+
style: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
enum: ['info', 'warning', 'tip', 'note'],
|
|
194
|
+
},
|
|
195
|
+
text: { type: 'string' },
|
|
196
|
+
},
|
|
197
|
+
required: ['style', 'text'],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
required: ['heading', 'body'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
filename: { type: 'string', description: 'Suggested filename without .pdf extension.' },
|
|
204
|
+
},
|
|
205
|
+
required: ['title', 'sections'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
handler: async (args) => {
|
|
209
|
+
try {
|
|
210
|
+
if (!args.title || typeof args.title !== 'string') {
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'VALIDATION_ERROR', message: 'title is required' }) }],
|
|
213
|
+
isError: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const sections = args.sections;
|
|
217
|
+
if (!Array.isArray(sections) || sections.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'VALIDATION_ERROR', message: 'sections must be a non-empty array' }) }],
|
|
220
|
+
isError: true,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const input = args;
|
|
224
|
+
const doc = buildReportDocument(input);
|
|
225
|
+
const bytes = await render(doc);
|
|
226
|
+
const base64 = toBase64(bytes);
|
|
227
|
+
const filename = (args.filename ?? `report-${Date.now()}`) + '.pdf';
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: JSON.stringify({ success: true, base64, filename, size_bytes: bytes.length }),
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return {
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
241
|
+
type: 'text',
|
|
242
|
+
text: JSON.stringify({
|
|
243
|
+
success: false,
|
|
244
|
+
error: err.code ?? 'UNKNOWN_ERROR',
|
|
245
|
+
message: err.message,
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const ELEMENTS_REFERENCE = `# pretext-pdf Element Types Reference
|
|
2
|
+
|
|
3
|
+
## paragraph
|
|
4
|
+
Renders a text block. Key props: \`text\` (required), \`fontSize\`, \`fontWeight\` (400|700), \`color\` (#hex), \`align\` (left|center|right|justify), \`spaceAfter\`, \`spaceBefore\`, \`bgColor\`, \`underline\`, \`strikethrough\`, \`url\`, \`letterSpacing\`, \`smallCaps\`.
|
|
5
|
+
Example: \`{ type: "paragraph", text: "Hello world", fontSize: 12, color: "#333333" }\`
|
|
6
|
+
|
|
7
|
+
## heading
|
|
8
|
+
Section heading with automatic bookmarks. Key props: \`level\` (1–4, required), \`text\` (required), \`fontSize\` (defaults: h1=28, h2=22, h3=18, h4=15), \`color\`, \`align\`, \`anchor\` (for internal links), \`bookmark\` (set false to exclude from PDF outline), \`spaceAfter\`.
|
|
9
|
+
Example: \`{ type: "heading", level: 1, text: "Introduction", color: "#1a1a2e" }\`
|
|
10
|
+
|
|
11
|
+
## rich-paragraph
|
|
12
|
+
Paragraph with per-span formatting. Key props: \`lines\` (array of RichLine, each with \`spans\` array). Each span: \`text\`, \`fontWeight\`, \`color\`, \`fontSize\`, \`italic\`, \`underline\`, \`strikethrough\`, \`url\`, \`href\` (internal anchor link).
|
|
13
|
+
Example: \`{ type: "rich-paragraph", lines: [{ spans: [{ text: "Bold", fontWeight: 700 }, { text: " normal" }] }] }\`
|
|
14
|
+
|
|
15
|
+
## table
|
|
16
|
+
Data table with optional header rows, borders, and column alignment. Key props: \`columns\` (array of \`{width, align}\`; width can be pt number, \`"2*"\` fraction, or \`"auto"\`), \`rows\` (array of \`{cells, isHeader, bgColor}\`), \`headerBgColor\`, \`borderColor\`, \`borderWidth\`, \`cellPaddingH\`, \`cellPaddingV\`, \`spaceAfter\`.
|
|
17
|
+
Cell props: \`text\`, \`fontWeight\`, \`color\`, \`bgColor\`, \`align\`, \`colSpan\`.
|
|
18
|
+
Example: \`{ type: "table", columns: [{ width: "2*" }, { width: 100, align: "right" }], rows: [{ isHeader: true, cells: [{ text: "Name", fontWeight: 700 }, { text: "Amount", fontWeight: 700 }] }] }\`
|
|
19
|
+
|
|
20
|
+
## list
|
|
21
|
+
Bulleted or numbered list. Key props: \`style\` (unordered|ordered, required), \`items\` (array of \`{text, children}\`), \`fontSize\`, \`color\`, \`spaceAfter\`, \`indent\` (pt for nested lists).
|
|
22
|
+
Example: \`{ type: "list", style: "unordered", items: [{ text: "Item one" }, { text: "Item two", children: [{ text: "Sub-item" }] }] }\`
|
|
23
|
+
|
|
24
|
+
## image
|
|
25
|
+
Embed PNG/JPG image. Key props: \`src\` (absolute file path or Uint8Array, required), \`width\` (pt, defaults to content width), \`height\` (optional, auto-scales), \`align\` (left|center|right), \`caption\`, \`spaceAfter\`.
|
|
26
|
+
Example: \`{ type: "image", src: "/abs/path/to/logo.png", width: 120, align: "center" }\`
|
|
27
|
+
|
|
28
|
+
## svg
|
|
29
|
+
Inline SVG vector graphic. Key props: \`content\` (SVG markup string, required), \`width\` (pt), \`height\` (pt), \`align\`, \`spaceAfter\`.
|
|
30
|
+
Example: \`{ type: "svg", content: "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40'/></svg>", width: 100, height: 100 }\`
|
|
31
|
+
|
|
32
|
+
## code
|
|
33
|
+
Monospace code block with optional syntax label. Key props: \`code\` (required), \`language\` (label only, no syntax highlighting), \`fontSize\`, \`bgColor\`, \`color\`, \`spaceAfter\`.
|
|
34
|
+
Example: \`{ type: "code", language: "typescript", code: "const x = 42;", bgColor: "#f4f4f4" }\`
|
|
35
|
+
|
|
36
|
+
## blockquote
|
|
37
|
+
Indented quote with a left border. Key props: \`text\` (required), \`borderColor\` (#hex), \`color\`, \`fontSize\`, \`spaceAfter\`.
|
|
38
|
+
Example: \`{ type: "blockquote", text: "The best code is no code at all.", borderColor: "#6366f1" }\`
|
|
39
|
+
|
|
40
|
+
## callout
|
|
41
|
+
Alert/info box with preset color schemes and optional title. Key props: \`content\` (body text, required), \`style\` (info|warning|tip|note — sets default colors), \`title\` (bold heading above body), \`borderColor\`, \`backgroundColor\`, \`color\`, \`fontSize\`, \`padding\`, \`spaceAfter\`.
|
|
42
|
+
Example: \`{ type: "callout", style: "warning", content: "This action cannot be undone." }\`
|
|
43
|
+
|
|
44
|
+
## toc
|
|
45
|
+
Auto-generated Table of Contents. Place after cover, before content. Key props: \`title\` (heading text), \`showTitle\` (boolean), \`leader\` (dot fill char, e.g. "."), \`minLevel\` (1–4), \`maxLevel\` (1–4), \`fontSize\`, \`spaceAfter\`.
|
|
46
|
+
Example: \`{ type: "toc", title: "Contents", showTitle: true, leader: ".", minLevel: 1, maxLevel: 2 }\`
|
|
47
|
+
|
|
48
|
+
## form-field
|
|
49
|
+
Interactive AcroForm field (text, checkbox, radio, dropdown, button). Key props: \`fieldType\` (required), \`name\` (unique, required), \`label\`, \`placeholder\`, \`defaultValue\`, \`multiline\`, \`options\` (for radio/dropdown), \`width\`, \`height\`, \`borderColor\`, \`spaceAfter\`.
|
|
50
|
+
Example: \`{ type: "form-field", fieldType: "text", name: "full_name", label: "Full Name", placeholder: "Enter your name" }\`
|
|
51
|
+
|
|
52
|
+
## comment
|
|
53
|
+
Invisible sticky-note annotation (shows as icon in PDF viewer). Key props: \`contents\` (popup text, required), \`author\`, \`color\` (#hex), \`open\` (show popup by default).
|
|
54
|
+
Example: \`{ type: "comment", contents: "Review this section.", author: "Jane" }\`
|
|
55
|
+
|
|
56
|
+
## hr
|
|
57
|
+
Horizontal rule / divider line. Key props: \`color\` (#hex), \`thickness\` (pt), \`spaceBelow\`.
|
|
58
|
+
Example: \`{ type: "hr", color: "#cccccc", thickness: 1, spaceBelow: 8 }\`
|
|
59
|
+
|
|
60
|
+
## spacer
|
|
61
|
+
Vertical whitespace. Key props: \`height\` (pt, required).
|
|
62
|
+
Example: \`{ type: "spacer", height: 24 }\`
|
|
63
|
+
|
|
64
|
+
## page-break
|
|
65
|
+
Force start of a new page. No additional props.
|
|
66
|
+
Example: \`{ type: "page-break" }\`
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Document-level options (PdfDocument)
|
|
71
|
+
- \`pageSize\`: 'A4'|'Letter'|'Legal'|'A3'|'A5' or \`[width, height]\` in pt. Default: 'A4'
|
|
72
|
+
- \`margins\`: \`{ top, bottom, left, right }\` in pt. Default: all 72pt
|
|
73
|
+
- \`defaultFont\`: font family name. Default: 'Inter'
|
|
74
|
+
- \`defaultFontSize\`: pt. Default: 12
|
|
75
|
+
- \`header\`: \`{ text, fontSize, align, color, fontFamily }\`. Supports \`{{pageNumber}}\` and \`{{totalPages}}\`
|
|
76
|
+
- \`footer\`: same as header
|
|
77
|
+
- \`watermark\`: \`{ text, opacity, rotation, fontSize, color }\`
|
|
78
|
+
- \`encryption\`: \`{ userPassword, ownerPassword, permissions }\`
|
|
79
|
+
- \`bookmarks\`: \`{ minLevel, maxLevel }\` or \`false\`
|
|
80
|
+
- \`metadata\`: \`{ title, author, subject, keywords, language }\`
|
|
81
|
+
- \`hyphenation\`: \`{ language: 'en-us' }\`
|
|
82
|
+
`;
|
|
83
|
+
export const listElementsTool = {
|
|
84
|
+
schema: {
|
|
85
|
+
name: 'list_element_types',
|
|
86
|
+
description: 'Returns a markdown reference of all pretext-pdf element types and their key properties. Use this before calling generate_pdf to understand what elements and options are available.',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {},
|
|
90
|
+
required: [],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
handler: async (_args) => {
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: ELEMENTS_REFERENCE }],
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pretext-pdf-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for pretext-pdf — generate professional PDFs from JSON in Claude, Cursor, or any AI agent",
|
|
5
|
+
"keywords": ["mcp", "pdf", "pdf-generation", "invoice", "report", "claude", "ai-agent", "pretext"],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"bin": { "pretext-pdf-mcp": "./dist/index.js" },
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"test": "tsx --test test/*.test.ts"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"pretext-pdf": "^0.4.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"tsx": "^4.0.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist/**/*", "smithery.yaml", "README.md", "LICENSE"],
|
|
24
|
+
"engines": { "node": ">=18.0.0" },
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Himanshu Jain",
|
|
27
|
+
"repository": { "type": "git", "url": "https://github.com/Himaan1998Y/pretext-pdf-mcp" },
|
|
28
|
+
"homepage": "https://github.com/Himaan1998Y/pretext-pdf-mcp#readme"
|
|
29
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
name: pretext-pdf
|
|
2
|
+
description: "Generate professional PDFs from structured JSON. Supports invoices (with GST), reports, tables, forms, encryption, and more. No headless browser — pure Node.js."
|
|
3
|
+
version: "1.0.0"
|
|
4
|
+
startCommand:
|
|
5
|
+
type: stdio
|
|
6
|
+
command: npx
|
|
7
|
+
args: ["-y", "pretext-pdf-mcp"]
|