autoform-mcp-server 1.2.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 +135 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +69 -0
- package/dist/services/batchGenerator.d.ts +10 -0
- package/dist/services/batchGenerator.js +105 -0
- package/dist/services/pdfService.d.ts +65 -0
- package/dist/services/pdfService.js +412 -0
- package/dist/services/templateStore.d.ts +20 -0
- package/dist/services/templateStore.js +137 -0
- package/dist/tools/detectFields.d.ts +20 -0
- package/dist/tools/detectFields.js +41 -0
- package/dist/tools/fillAtCoordinates.d.ts +94 -0
- package/dist/tools/fillAtCoordinates.js +113 -0
- package/dist/tools/fillBatchAtCoordinates.d.ts +88 -0
- package/dist/tools/fillBatchAtCoordinates.js +102 -0
- package/dist/tools/fillPdf.d.ts +42 -0
- package/dist/tools/fillPdf.js +97 -0
- package/dist/tools/generateBatch.d.ts +46 -0
- package/dist/tools/generateBatch.js +106 -0
- package/dist/tools/importTemplate.d.ts +20 -0
- package/dist/tools/importTemplate.js +36 -0
- package/dist/tools/manageTemplates.d.ts +15 -0
- package/dist/tools/manageTemplates.js +44 -0
- package/dist/tools/saveCoordinatesAsTemplate.d.ts +78 -0
- package/dist/tools/saveCoordinatesAsTemplate.js +141 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +2 -0
- package/output/.gitkeep +0 -0
- package/package.json +52 -0
- package/templates/.gitkeep +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AutoForm MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server for bulk PDF form filling. Detect fields, fill templates, and generate hundreds of PDFs from data — directly from Claude.
|
|
4
|
+
|
|
5
|
+
## Quick Setup
|
|
6
|
+
|
|
7
|
+
### Claude Code
|
|
8
|
+
|
|
9
|
+
Create `.mcp.json` in your project root:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"autoform": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "autoform-mcp-server"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Claude Desktop
|
|
23
|
+
|
|
24
|
+
Add to your `claude_desktop_config.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"autoform": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "autoform-mcp-server"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Config file location:
|
|
38
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
39
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
40
|
+
|
|
41
|
+
## Tools (9)
|
|
42
|
+
|
|
43
|
+
### PDF Analysis
|
|
44
|
+
| Tool | Description |
|
|
45
|
+
|------|-------------|
|
|
46
|
+
| `autoform_get_pdf_info` | Get page count and dimensions of a PDF |
|
|
47
|
+
| `autoform_detect_fields` | Detect AcroForm fields in interactive PDFs |
|
|
48
|
+
|
|
49
|
+
### Fill Single PDF
|
|
50
|
+
| Tool | Description |
|
|
51
|
+
|------|-------------|
|
|
52
|
+
| `autoform_fill_pdf` | Fill a PDF using AcroForm or a saved template |
|
|
53
|
+
| `autoform_fill_at_coordinates` | Write text at exact coordinates (for static PDFs Claude analyzes visually) |
|
|
54
|
+
|
|
55
|
+
### Batch Generation
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `autoform_fill_batch_at_coordinates` | Generate multiple PDFs from one template + data, using coordinates |
|
|
59
|
+
| `autoform_generate_batch` | Generate multiple PDFs using a saved template + data array |
|
|
60
|
+
|
|
61
|
+
### Template Management
|
|
62
|
+
| Tool | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `autoform_save_coordinates_as_template` | Save PDF + field coordinates as a reusable template |
|
|
65
|
+
| `autoform_import_template` | Import a template JSON exported from the AutoForm web app |
|
|
66
|
+
| `autoform_list_templates` | List all saved templates |
|
|
67
|
+
|
|
68
|
+
## Usage Examples
|
|
69
|
+
|
|
70
|
+
### Fill a certificate (Claude analyzes the PDF visually)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
You: "Fill this certificate with the name Juan Pérez"
|
|
74
|
+
|
|
75
|
+
Claude:
|
|
76
|
+
1. Gets PDF dimensions with autoform_get_pdf_info
|
|
77
|
+
2. Analyzes the PDF visually to find where the name goes
|
|
78
|
+
3. Calls autoform_fill_at_coordinates with the exact position
|
|
79
|
+
→ Generates filled certificate
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Batch generate from data
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
You: "Generate certificates for: Ana García, Pedro López, María Torres"
|
|
86
|
+
|
|
87
|
+
Claude:
|
|
88
|
+
1. Calls autoform_fill_batch_at_coordinates with positions + data
|
|
89
|
+
→ Generates 3 PDFs + optional merged PDF
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Save and reuse a template
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
You: "Save this certificate layout for future use"
|
|
96
|
+
|
|
97
|
+
Claude:
|
|
98
|
+
1. Calls autoform_save_coordinates_as_template
|
|
99
|
+
→ Template saved
|
|
100
|
+
|
|
101
|
+
You: "Generate 50 certificates using the saved template with this data..."
|
|
102
|
+
|
|
103
|
+
Claude:
|
|
104
|
+
1. Calls autoform_generate_batch with template name + data
|
|
105
|
+
→ Generates 50 PDFs
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Fill interactive PDF forms (AcroForm)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
You: "Detect and fill the fields in this form"
|
|
112
|
+
|
|
113
|
+
Claude:
|
|
114
|
+
1. Calls autoform_detect_fields → finds form fields
|
|
115
|
+
2. Calls autoform_fill_pdf with field values
|
|
116
|
+
→ Generates filled form
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Distribution Modes
|
|
120
|
+
|
|
121
|
+
When a template has repeated fields (e.g., 3 fields named "nombre"):
|
|
122
|
+
|
|
123
|
+
- **`sequential`** (default): Each field consumes a different data row. 9 rows + 3 fields = 3 documents.
|
|
124
|
+
- **`repeat`**: One row fills ALL fields with the same name. 9 rows = 9 documents.
|
|
125
|
+
|
|
126
|
+
## How It Works
|
|
127
|
+
|
|
128
|
+
- Templates and generated PDFs are stored locally in the MCP server directory
|
|
129
|
+
- Uses `pdf-lib` for PDF manipulation (no external services)
|
|
130
|
+
- All processing happens locally — no data leaves your machine
|
|
131
|
+
- Zero vulnerabilities — only 3 dependencies
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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 { handleDetectFields, detectFieldsSchema } from './tools/detectFields.js';
|
|
6
|
+
import { handleFillPdf, fillPdfSchema } from './tools/fillPdf.js';
|
|
7
|
+
import { handleFillAtCoordinates, fillAtCoordinatesSchema, handleGetPdfInfo, getPdfInfoSchema } from './tools/fillAtCoordinates.js';
|
|
8
|
+
import { handleFillBatchAtCoordinates, fillBatchAtCoordinatesSchema } from './tools/fillBatchAtCoordinates.js';
|
|
9
|
+
import { handleSaveCoordinatesAsTemplate, saveCoordinatesAsTemplateSchema } from './tools/saveCoordinatesAsTemplate.js';
|
|
10
|
+
import { handleGenerateBatch, generateBatchSchema } from './tools/generateBatch.js';
|
|
11
|
+
import { handleListTemplates, listTemplatesSchema } from './tools/manageTemplates.js';
|
|
12
|
+
import { handleImportTemplate, importTemplateSchema } from './tools/importTemplate.js';
|
|
13
|
+
const server = new Server({ name: 'autoform', version: '1.2.0' }, { capabilities: { tools: {} } });
|
|
14
|
+
// Register all tools
|
|
15
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16
|
+
tools: [
|
|
17
|
+
// Info & detection
|
|
18
|
+
getPdfInfoSchema,
|
|
19
|
+
detectFieldsSchema,
|
|
20
|
+
// Single fill
|
|
21
|
+
fillPdfSchema,
|
|
22
|
+
fillAtCoordinatesSchema,
|
|
23
|
+
// Batch fill
|
|
24
|
+
fillBatchAtCoordinatesSchema,
|
|
25
|
+
generateBatchSchema,
|
|
26
|
+
// Template management
|
|
27
|
+
saveCoordinatesAsTemplateSchema,
|
|
28
|
+
listTemplatesSchema,
|
|
29
|
+
importTemplateSchema
|
|
30
|
+
]
|
|
31
|
+
}));
|
|
32
|
+
// Handle tool calls
|
|
33
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
34
|
+
const { name, arguments: args } = request.params;
|
|
35
|
+
try {
|
|
36
|
+
switch (name) {
|
|
37
|
+
case 'autoform_get_pdf_info':
|
|
38
|
+
return await handleGetPdfInfo(args);
|
|
39
|
+
case 'autoform_detect_fields':
|
|
40
|
+
return await handleDetectFields(args);
|
|
41
|
+
case 'autoform_fill_pdf':
|
|
42
|
+
return await handleFillPdf(args);
|
|
43
|
+
case 'autoform_fill_at_coordinates':
|
|
44
|
+
return await handleFillAtCoordinates(args);
|
|
45
|
+
case 'autoform_fill_batch_at_coordinates':
|
|
46
|
+
return await handleFillBatchAtCoordinates(args);
|
|
47
|
+
case 'autoform_save_coordinates_as_template':
|
|
48
|
+
return await handleSaveCoordinatesAsTemplate(args);
|
|
49
|
+
case 'autoform_generate_batch':
|
|
50
|
+
return await handleGenerateBatch(args);
|
|
51
|
+
case 'autoform_list_templates':
|
|
52
|
+
return await handleListTemplates();
|
|
53
|
+
case 'autoform_import_template':
|
|
54
|
+
return await handleImportTemplate(args);
|
|
55
|
+
default:
|
|
56
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
62
|
+
isError: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// Start server
|
|
67
|
+
const transport = new StdioServerTransport();
|
|
68
|
+
await server.connect(transport);
|
|
69
|
+
console.error('[AutoForm MCP] Server v1.2.0 started — 9 tools registered');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SavedTemplate, ParsedData, DistributionMode, GenerationResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Batch PDF generation with distribution logic.
|
|
4
|
+
* Adapts web app's distributionEngine + pdfGenerator for Node.js.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BatchGenerator {
|
|
7
|
+
static generate(template: SavedTemplate, data: ParsedData[], mode?: DistributionMode, mergeIntoSingle?: boolean): Promise<GenerationResult>;
|
|
8
|
+
private static calculateDocumentsNeeded;
|
|
9
|
+
private static createMappings;
|
|
10
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { PdfService } from './pdfService.js';
|
|
4
|
+
const OUTPUT_DIR = path.resolve(import.meta.dirname, '../../output');
|
|
5
|
+
/**
|
|
6
|
+
* Batch PDF generation with distribution logic.
|
|
7
|
+
* Adapts web app's distributionEngine + pdfGenerator for Node.js.
|
|
8
|
+
*/
|
|
9
|
+
export class BatchGenerator {
|
|
10
|
+
static async generate(template, data, mode = 'sequential', mergeIntoSingle = false) {
|
|
11
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
const timestamp = new Date().toISOString().slice(0, 10);
|
|
13
|
+
const safeName = template.name.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
14
|
+
// Read base PDF
|
|
15
|
+
const pdfBytes = fs.readFileSync(template.pdfPath);
|
|
16
|
+
const basePdf = new Uint8Array(pdfBytes);
|
|
17
|
+
// Calculate distribution
|
|
18
|
+
const documentsNeeded = this.calculateDocumentsNeeded(data, template, mode);
|
|
19
|
+
if (documentsNeeded === 0) {
|
|
20
|
+
return { documentsGenerated: 0, outputPaths: [], errors: ['No hay datos para generar documentos'] };
|
|
21
|
+
}
|
|
22
|
+
const outputPaths = [];
|
|
23
|
+
const errors = [];
|
|
24
|
+
for (let docNum = 0; docNum < documentsNeeded; docNum++) {
|
|
25
|
+
try {
|
|
26
|
+
const mappings = this.createMappings(data, template, docNum, mode);
|
|
27
|
+
const docNumStr = String(docNum + 1).padStart(3, '0');
|
|
28
|
+
const outputPath = path.join(OUTPUT_DIR, `${safeName}_${docNumStr}_${timestamp}.pdf`);
|
|
29
|
+
await PdfService.generateSingleDocument(basePdf, mappings, outputPath);
|
|
30
|
+
outputPaths.push(outputPath);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
errors.push(`Doc ${docNum + 1}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Merge if requested
|
|
37
|
+
let mergedPath;
|
|
38
|
+
if (mergeIntoSingle && outputPaths.length > 0) {
|
|
39
|
+
mergedPath = path.join(OUTPUT_DIR, `${safeName}_merged_${timestamp}.pdf`);
|
|
40
|
+
await PdfService.mergePdfs(outputPaths, mergedPath);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
documentsGenerated: outputPaths.length,
|
|
44
|
+
outputPaths,
|
|
45
|
+
mergedPath,
|
|
46
|
+
errors
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
static calculateDocumentsNeeded(data, template, mode) {
|
|
50
|
+
if (data.length === 0 || template.analysis.fieldGroups.length === 0)
|
|
51
|
+
return 0;
|
|
52
|
+
if (mode === 'repeat')
|
|
53
|
+
return data.length;
|
|
54
|
+
// Sequential: each field with the same name consumes a different row
|
|
55
|
+
return Math.max(...template.analysis.fieldGroups.map(group => {
|
|
56
|
+
const values = data.map(row => row[group.fieldName]).filter(v => v !== null && v !== undefined);
|
|
57
|
+
return Math.ceil(values.length / group.count);
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
static createMappings(data, template, docNum, mode) {
|
|
61
|
+
const mappings = [];
|
|
62
|
+
// Group fields by name, sorted by page then Y
|
|
63
|
+
const fieldsByName = {};
|
|
64
|
+
for (const f of template.fields) {
|
|
65
|
+
const name = f.fieldName || f.name;
|
|
66
|
+
if (!fieldsByName[name])
|
|
67
|
+
fieldsByName[name] = [];
|
|
68
|
+
fieldsByName[name].push(f);
|
|
69
|
+
}
|
|
70
|
+
for (const group of Object.values(fieldsByName)) {
|
|
71
|
+
group.sort((a, b) => a.pageNumber !== b.pageNumber ? a.pageNumber - b.pageNumber : a.y - b.y);
|
|
72
|
+
}
|
|
73
|
+
if (mode === 'repeat') {
|
|
74
|
+
// All fields with the same name get the SAME value from one row
|
|
75
|
+
const row = data[docNum];
|
|
76
|
+
if (!row)
|
|
77
|
+
return mappings;
|
|
78
|
+
for (const [fieldName, fields] of Object.entries(fieldsByName)) {
|
|
79
|
+
const value = row[fieldName];
|
|
80
|
+
for (const field of fields) {
|
|
81
|
+
mappings.push({ field, value });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Sequential: each field consumes a different row
|
|
87
|
+
const columnArrays = {};
|
|
88
|
+
for (const group of template.analysis.fieldGroups) {
|
|
89
|
+
columnArrays[group.fieldName] = data.map(r => r[group.fieldName]).filter(v => v !== null && v !== undefined);
|
|
90
|
+
}
|
|
91
|
+
for (const group of template.analysis.fieldGroups) {
|
|
92
|
+
const fields = fieldsByName[group.fieldName] || [];
|
|
93
|
+
const colArr = columnArrays[group.fieldName] || [];
|
|
94
|
+
for (let fi = 0; fi < fields.length; fi++) {
|
|
95
|
+
const arrIdx = docNum * group.count + fi;
|
|
96
|
+
mappings.push({
|
|
97
|
+
field: fields[fi],
|
|
98
|
+
value: arrIdx < colArr.length ? colArr[arrIdx] : null
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return mappings;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DetectedField, FieldBox } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* PDF field detection and filling service.
|
|
4
|
+
* Uses pdf-lib's AcroForm API for detection + drawText for template-based filling.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PdfService {
|
|
7
|
+
/** Detect AcroForm fields in a PDF */
|
|
8
|
+
static detectFields(pdfPath: string): Promise<{
|
|
9
|
+
fields: DetectedField[];
|
|
10
|
+
hasAcroform: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
/** Fill AcroForm fields and save */
|
|
13
|
+
static fillAcroForm(pdfPath: string, values: Record<string, string>, outputPath: string): Promise<number>;
|
|
14
|
+
/** Fill PDF using template coordinates (drawText), same logic as web app's pdfGenerator */
|
|
15
|
+
static fillWithTemplate(pdfPath: string, fields: FieldBox[], values: Record<string, string>, outputPath: string): Promise<number>;
|
|
16
|
+
/**
|
|
17
|
+
* Fill PDF at exact coordinates (points, PDF native system — origin bottom-left).
|
|
18
|
+
* Designed for Claude to pass positions it determined visually.
|
|
19
|
+
*/
|
|
20
|
+
static fillAtCoordinates(pdfPath: string, fields: Array<{
|
|
21
|
+
text: string;
|
|
22
|
+
page: number;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
fontSize?: number;
|
|
28
|
+
overflowMode?: 'shrink' | 'wrap';
|
|
29
|
+
}>, outputPath: string): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Batch fill: generates multiple PDFs from the same template with different data sets.
|
|
32
|
+
* Each entry in `dataRows` produces one PDF using the same field positions.
|
|
33
|
+
*/
|
|
34
|
+
static batchFillAtCoordinates(pdfPath: string, fieldDefinitions: Array<{
|
|
35
|
+
label: string;
|
|
36
|
+
page: number;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
fontSize?: number;
|
|
42
|
+
overflowMode?: 'shrink' | 'wrap';
|
|
43
|
+
}>, dataRows: Array<Record<string, string>>, outputDir: string, baseName: string, merge?: boolean): Promise<{
|
|
44
|
+
outputPaths: string[];
|
|
45
|
+
mergedPath?: string;
|
|
46
|
+
errors: string[];
|
|
47
|
+
}>;
|
|
48
|
+
/** Get PDF page dimensions (useful for Claude to calculate coordinates) */
|
|
49
|
+
static getPageDimensions(pdfPath: string): Promise<Array<{
|
|
50
|
+
page: number;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
}>>;
|
|
54
|
+
/** Generate a single document with field mappings (for batch generation) */
|
|
55
|
+
static generateSingleDocument(basePdfBytes: Uint8Array, mappings: {
|
|
56
|
+
field: FieldBox;
|
|
57
|
+
value: any;
|
|
58
|
+
}[], outputPath: string): Promise<void>;
|
|
59
|
+
/** Merge multiple PDFs into one */
|
|
60
|
+
static mergePdfs(pdfPaths: string[], outputPath: string): Promise<void>;
|
|
61
|
+
private static drawFieldText;
|
|
62
|
+
private static sanitize;
|
|
63
|
+
private static optimalFontSize;
|
|
64
|
+
private static wrapText;
|
|
65
|
+
}
|