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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }