autoform-mcp-server 1.2.1 → 1.4.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/dist/index.js +7 -3
- package/dist/services/batchGenerator.js +23 -2
- package/dist/services/pdfService.d.ts +10 -0
- package/dist/services/pdfService.js +98 -2
- package/dist/tools/detectFields.js +12 -1
- package/dist/tools/fillAtCoordinates.js +9 -1
- package/dist/tools/fillBatchAcroForm.d.ts +52 -0
- package/dist/tools/fillBatchAcroForm.js +96 -0
- package/dist/tools/fillBatchAtCoordinates.js +7 -0
- package/dist/tools/fillPdf.js +9 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,12 @@ import { handleDetectFields, detectFieldsSchema } from './tools/detectFields.js'
|
|
|
6
6
|
import { handleFillPdf, fillPdfSchema } from './tools/fillPdf.js';
|
|
7
7
|
import { handleFillAtCoordinates, fillAtCoordinatesSchema, handleGetPdfInfo, getPdfInfoSchema } from './tools/fillAtCoordinates.js';
|
|
8
8
|
import { handleFillBatchAtCoordinates, fillBatchAtCoordinatesSchema } from './tools/fillBatchAtCoordinates.js';
|
|
9
|
+
import { handleFillBatchAcroForm, fillBatchAcroFormSchema } from './tools/fillBatchAcroForm.js';
|
|
9
10
|
import { handleSaveCoordinatesAsTemplate, saveCoordinatesAsTemplateSchema } from './tools/saveCoordinatesAsTemplate.js';
|
|
10
11
|
import { handleGenerateBatch, generateBatchSchema } from './tools/generateBatch.js';
|
|
11
12
|
import { handleListTemplates, listTemplatesSchema } from './tools/manageTemplates.js';
|
|
12
13
|
import { handleImportTemplate, importTemplateSchema } from './tools/importTemplate.js';
|
|
13
|
-
const server = new Server({ name: 'autoform', version: '1.
|
|
14
|
+
const server = new Server({ name: 'autoform', version: '1.4.0' }, { capabilities: { tools: {} } });
|
|
14
15
|
// Register all tools
|
|
15
16
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16
17
|
tools: [
|
|
@@ -20,7 +21,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
20
21
|
// Single fill
|
|
21
22
|
fillPdfSchema,
|
|
22
23
|
fillAtCoordinatesSchema,
|
|
23
|
-
// Batch fill
|
|
24
|
+
// Batch fill (preferred for multiple documents)
|
|
25
|
+
fillBatchAcroFormSchema,
|
|
24
26
|
fillBatchAtCoordinatesSchema,
|
|
25
27
|
generateBatchSchema,
|
|
26
28
|
// Template management
|
|
@@ -44,6 +46,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
44
46
|
return await handleFillAtCoordinates(args);
|
|
45
47
|
case 'autoform_fill_batch_at_coordinates':
|
|
46
48
|
return await handleFillBatchAtCoordinates(args);
|
|
49
|
+
case 'autoform_fill_batch_acroform':
|
|
50
|
+
return await handleFillBatchAcroForm(args);
|
|
47
51
|
case 'autoform_save_coordinates_as_template':
|
|
48
52
|
return await handleSaveCoordinatesAsTemplate(args);
|
|
49
53
|
case 'autoform_generate_batch':
|
|
@@ -66,4 +70,4 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
66
70
|
// Start server
|
|
67
71
|
const transport = new StdioServerTransport();
|
|
68
72
|
await server.connect(transport);
|
|
69
|
-
console.error('[AutoForm MCP] Server v1.
|
|
73
|
+
console.error('[AutoForm MCP] Server v1.4.0 started — 10 tools registered');
|
|
@@ -23,11 +23,15 @@ export class BatchGenerator {
|
|
|
23
23
|
}
|
|
24
24
|
const outputPaths = [];
|
|
25
25
|
const errors = [];
|
|
26
|
+
// If merging, write individuals to temp dir (will be cleaned up)
|
|
27
|
+
const workingDir = mergeIntoSingle
|
|
28
|
+
? fs.mkdtempSync(path.join(OUTPUT_DIR, '.tmp_batch_'))
|
|
29
|
+
: OUTPUT_DIR;
|
|
26
30
|
for (let docNum = 0; docNum < documentsNeeded; docNum++) {
|
|
27
31
|
try {
|
|
28
32
|
const mappings = this.createMappings(data, template, docNum, mode);
|
|
29
33
|
const docNumStr = String(docNum + 1).padStart(3, '0');
|
|
30
|
-
const outputPath = path.join(
|
|
34
|
+
const outputPath = path.join(workingDir, `${safeName}_${docNumStr}_${timestamp}.pdf`);
|
|
31
35
|
await PdfService.generateSingleDocument(basePdf, mappings, outputPath);
|
|
32
36
|
outputPaths.push(outputPath);
|
|
33
37
|
}
|
|
@@ -35,11 +39,28 @@ export class BatchGenerator {
|
|
|
35
39
|
errors.push(`Doc ${docNum + 1}: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
|
-
// Merge if requested
|
|
42
|
+
// Merge if requested — and clean up individuals
|
|
39
43
|
let mergedPath;
|
|
40
44
|
if (mergeIntoSingle && outputPaths.length > 0) {
|
|
41
45
|
mergedPath = path.join(OUTPUT_DIR, `${safeName}_merged_${timestamp}.pdf`);
|
|
42
46
|
await PdfService.mergePdfs(outputPaths, mergedPath);
|
|
47
|
+
// Delete temp files and dir
|
|
48
|
+
for (const p of outputPaths) {
|
|
49
|
+
try {
|
|
50
|
+
fs.unlinkSync(p);
|
|
51
|
+
}
|
|
52
|
+
catch { /* ignore */ }
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
fs.rmdirSync(workingDir);
|
|
56
|
+
}
|
|
57
|
+
catch { /* ignore */ }
|
|
58
|
+
return {
|
|
59
|
+
documentsGenerated: outputPaths.length,
|
|
60
|
+
outputPaths: [],
|
|
61
|
+
mergedPath,
|
|
62
|
+
errors
|
|
63
|
+
};
|
|
43
64
|
}
|
|
44
65
|
return {
|
|
45
66
|
documentsGenerated: outputPaths.length,
|
|
@@ -11,6 +11,16 @@ export declare class PdfService {
|
|
|
11
11
|
}>;
|
|
12
12
|
/** Fill AcroForm fields and save */
|
|
13
13
|
static fillAcroForm(pdfPath: string, values: Record<string, string>, outputPath: string): Promise<number>;
|
|
14
|
+
/**
|
|
15
|
+
* Batch fill AcroForm: generate N PDFs from the same base PDF, one per data row.
|
|
16
|
+
* If merge=true, all outputs are concatenated into a single PDF and temp files are cleaned.
|
|
17
|
+
* Optionally accepts a fieldMap to translate logical data keys → technical AcroForm field names.
|
|
18
|
+
*/
|
|
19
|
+
static batchFillAcroForm(pdfPath: string, dataRows: Array<Record<string, string>>, outputDir: string, baseName: string, merge?: boolean, fieldMap?: Record<string, string>): Promise<{
|
|
20
|
+
outputPaths: string[];
|
|
21
|
+
mergedPath?: string;
|
|
22
|
+
errors: string[];
|
|
23
|
+
}>;
|
|
14
24
|
/** Fill PDF using template coordinates (drawText), same logic as web app's pdfGenerator */
|
|
15
25
|
static fillWithTemplate(pdfPath: string, fields: FieldBox[], values: Record<string, string>, outputPath: string): Promise<number>;
|
|
16
26
|
/**
|
|
@@ -98,6 +98,86 @@ export class PdfService {
|
|
|
98
98
|
fs.writeFileSync(outputPath, savedBytes);
|
|
99
99
|
return filled;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Batch fill AcroForm: generate N PDFs from the same base PDF, one per data row.
|
|
103
|
+
* If merge=true, all outputs are concatenated into a single PDF and temp files are cleaned.
|
|
104
|
+
* Optionally accepts a fieldMap to translate logical data keys → technical AcroForm field names.
|
|
105
|
+
*/
|
|
106
|
+
static async batchFillAcroForm(pdfPath, dataRows, outputDir, baseName, merge = false, fieldMap) {
|
|
107
|
+
const outputPaths = [];
|
|
108
|
+
const errors = [];
|
|
109
|
+
const timestamp = new Date().toISOString().slice(0, 10);
|
|
110
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
111
|
+
const workingDir = merge
|
|
112
|
+
? fs.mkdtempSync(path.join(outputDir, '.tmp_batch_'))
|
|
113
|
+
: outputDir;
|
|
114
|
+
// Load the base PDF once
|
|
115
|
+
const bytes = fs.readFileSync(pdfPath);
|
|
116
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
117
|
+
try {
|
|
118
|
+
const row = dataRows[i];
|
|
119
|
+
// Translate logical keys to technical field names if a map is provided
|
|
120
|
+
const resolvedValues = {};
|
|
121
|
+
for (const [key, value] of Object.entries(row)) {
|
|
122
|
+
const technicalName = fieldMap?.[key] ?? key;
|
|
123
|
+
resolvedValues[technicalName] = String(value ?? '');
|
|
124
|
+
}
|
|
125
|
+
const num = String(i + 1).padStart(3, '0');
|
|
126
|
+
const outPath = path.join(workingDir, `${baseName}_${num}_${timestamp}.pdf`);
|
|
127
|
+
// Re-load each iteration to get a fresh form instance
|
|
128
|
+
const pdfDoc = await PDFDocument.load(bytes);
|
|
129
|
+
const form = pdfDoc.getForm();
|
|
130
|
+
for (const [name, value] of Object.entries(resolvedValues)) {
|
|
131
|
+
try {
|
|
132
|
+
const field = form.getField(name);
|
|
133
|
+
if (field instanceof PDFTextField) {
|
|
134
|
+
field.setText(value);
|
|
135
|
+
}
|
|
136
|
+
else if (field instanceof PDFCheckBox) {
|
|
137
|
+
if (['true', '1', 'yes', 'si', 'sí'].includes(value.toLowerCase()))
|
|
138
|
+
field.check();
|
|
139
|
+
else
|
|
140
|
+
field.uncheck();
|
|
141
|
+
}
|
|
142
|
+
else if (field instanceof PDFDropdown) {
|
|
143
|
+
field.select(value);
|
|
144
|
+
}
|
|
145
|
+
else if (field instanceof PDFRadioGroup) {
|
|
146
|
+
field.select(value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Field not found — skip silently
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
form.flatten();
|
|
154
|
+
const savedBytes = await pdfDoc.save();
|
|
155
|
+
fs.writeFileSync(outPath, savedBytes);
|
|
156
|
+
outputPaths.push(outPath);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
errors.push(`Fila ${i + 1}: ${err instanceof Error ? err.message : String(err)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let mergedPath;
|
|
163
|
+
if (merge && outputPaths.length > 0) {
|
|
164
|
+
mergedPath = path.join(outputDir, `${baseName}_merged_${timestamp}.pdf`);
|
|
165
|
+
await this.mergePdfs(outputPaths, mergedPath);
|
|
166
|
+
// Clean up temp files and dir
|
|
167
|
+
for (const p of outputPaths) {
|
|
168
|
+
try {
|
|
169
|
+
fs.unlinkSync(p);
|
|
170
|
+
}
|
|
171
|
+
catch { /* ignore */ }
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
fs.rmdirSync(workingDir);
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
return { outputPaths: [], mergedPath, errors };
|
|
178
|
+
}
|
|
179
|
+
return { outputPaths, mergedPath, errors };
|
|
180
|
+
}
|
|
101
181
|
/** Fill PDF using template coordinates (drawText), same logic as web app's pdfGenerator */
|
|
102
182
|
static async fillWithTemplate(pdfPath, fields, values, outputPath) {
|
|
103
183
|
const bytes = fs.readFileSync(pdfPath);
|
|
@@ -202,11 +282,14 @@ export class PdfService {
|
|
|
202
282
|
* Each entry in `dataRows` produces one PDF using the same field positions.
|
|
203
283
|
*/
|
|
204
284
|
static async batchFillAtCoordinates(pdfPath, fieldDefinitions, dataRows, outputDir, baseName, merge = false) {
|
|
205
|
-
const bytes = fs.readFileSync(pdfPath);
|
|
206
285
|
const outputPaths = [];
|
|
207
286
|
const errors = [];
|
|
208
287
|
const timestamp = new Date().toISOString().slice(0, 10);
|
|
209
288
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
289
|
+
// If merging, write individuals to a temp dir that will be cleaned up
|
|
290
|
+
const workingDir = merge
|
|
291
|
+
? fs.mkdtempSync(path.join(outputDir, '.tmp_batch_'))
|
|
292
|
+
: outputDir;
|
|
210
293
|
for (let i = 0; i < dataRows.length; i++) {
|
|
211
294
|
try {
|
|
212
295
|
const row = dataRows[i];
|
|
@@ -221,7 +304,7 @@ export class PdfService {
|
|
|
221
304
|
overflowMode: def.overflowMode
|
|
222
305
|
})).filter(f => f.text.trim() !== '');
|
|
223
306
|
const num = String(i + 1).padStart(3, '0');
|
|
224
|
-
const outPath = path.join(
|
|
307
|
+
const outPath = path.join(workingDir, `${baseName}_${num}_${timestamp}.pdf`);
|
|
225
308
|
await this.fillAtCoordinates(pdfPath, fields, outPath);
|
|
226
309
|
outputPaths.push(outPath);
|
|
227
310
|
}
|
|
@@ -233,6 +316,19 @@ export class PdfService {
|
|
|
233
316
|
if (merge && outputPaths.length > 0) {
|
|
234
317
|
mergedPath = path.join(outputDir, `${baseName}_merged_${timestamp}.pdf`);
|
|
235
318
|
await this.mergePdfs(outputPaths, mergedPath);
|
|
319
|
+
// Clean up temp files and dir
|
|
320
|
+
for (const p of outputPaths) {
|
|
321
|
+
try {
|
|
322
|
+
fs.unlinkSync(p);
|
|
323
|
+
}
|
|
324
|
+
catch { /* ignore */ }
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
fs.rmdirSync(workingDir);
|
|
328
|
+
}
|
|
329
|
+
catch { /* ignore */ }
|
|
330
|
+
// Clear individual paths from response since they no longer exist
|
|
331
|
+
return { outputPaths: [], mergedPath, errors };
|
|
236
332
|
}
|
|
237
333
|
return { outputPaths, mergedPath, errors };
|
|
238
334
|
}
|
|
@@ -5,7 +5,18 @@ export const detectFieldsSchema = {
|
|
|
5
5
|
|
|
6
6
|
CUANDO USAR: El PDF es un formulario donde puedes hacer click y escribir. Si el PDF es una imagen/diseño estatico (certificado, diploma), esta tool dira "sin campos" — en ese caso usa autoform_get_pdf_info + analisis visual.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
⚠️ IMPORTANTE — Los nombres tecnicos NO son los labels visibles:
|
|
9
|
+
- Esta tool devuelve nombres TECNICOS internos del PDF (ej: "Text1", "fld_001", "untitled_field"), que pueden NO coincidir con los labels visibles ("Nombre:", "Apellido:", "DNI:").
|
|
10
|
+
- NO asumas el significado de cada campo por su nombre tecnico ni por su posicion.
|
|
11
|
+
- DESPUES de detectar los campos, DEBES mirar el PDF visualmente (ya puedes ver PDFs como imagenes) y correlacionar cada campo tecnico con su label visible usando las coordenadas (x, y, page) que devuelve esta tool.
|
|
12
|
+
- Solo DESPUES de esa correlacion visual procede a llenar con los datos correctos.
|
|
13
|
+
|
|
14
|
+
FLUJO RECOMENDADO:
|
|
15
|
+
1. autoform_detect_fields → obtienes lista de campos tecnicos con coordenadas
|
|
16
|
+
2. MIRAS EL PDF VISUALMENTE → asocias cada coordenada con el label visible cercano
|
|
17
|
+
3. Decides que dato va en que campo tecnico
|
|
18
|
+
4. Para UN documento: autoform_fill_pdf con field_values={"nombre_tecnico": "valor"}
|
|
19
|
+
5. Para MULTIPLES documentos (batch): autoform_fill_batch_acroform con data_rows y field_map si las claves son logicas.`,
|
|
9
20
|
inputSchema: {
|
|
10
21
|
type: 'object',
|
|
11
22
|
properties: {
|
|
@@ -4,9 +4,17 @@ import { PdfService } from '../services/pdfService.js';
|
|
|
4
4
|
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
5
5
|
export const fillAtCoordinatesSchema = {
|
|
6
6
|
name: 'autoform_fill_at_coordinates',
|
|
7
|
-
description: `Escribe texto en posiciones exactas de
|
|
7
|
+
description: `Escribe texto en posiciones exactas de UN PDF usando coordenadas en puntos PDF (origen: esquina inferior-izquierda).
|
|
8
8
|
Ideal cuando Claude analiza visualmente un PDF y determina dónde colocar el texto.
|
|
9
9
|
|
|
10
|
+
⚠️ ESTA TOOL ES PARA UN UNICO DOCUMENTO. Si necesitas generar multiples PDFs con los mismos campos pero datos diferentes, USA autoform_fill_batch_at_coordinates (mas eficiente, una sola llamada).
|
|
11
|
+
|
|
12
|
+
IMPORTANTE — Codificación de texto:
|
|
13
|
+
- El texto se pasa EXACTAMENTE como lo envías. NO simplifiques ni modifiques caracteres.
|
|
14
|
+
- Acentos españoles (á é í ó ú ñ Ñ ü) están soportados completamente.
|
|
15
|
+
- Nombres como "Sofía", "Benítez", "Peña" deben pasarse con sus tildes intactas.
|
|
16
|
+
- Solo se filtran: emojis, caracteres de control invisibles, y patrones de script malicioso.
|
|
17
|
+
|
|
10
18
|
FLUJO RECOMENDADO:
|
|
11
19
|
1. Primero usa autoform_get_pdf_info para obtener las dimensiones de cada página
|
|
12
20
|
2. Basándote en el análisis visual del PDF, calcula las coordenadas en puntos PDF
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export declare const fillBatchAcroFormSchema: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
pdf_path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
data_rows: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
items: {
|
|
15
|
+
type: string;
|
|
16
|
+
additionalProperties: {
|
|
17
|
+
type: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
field_map: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
additionalProperties: {
|
|
25
|
+
type: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
merge_into_single: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
output_dir: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
required: string[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export declare function handleFillBatchAcroForm(args: any): Promise<{
|
|
41
|
+
content: {
|
|
42
|
+
type: "text";
|
|
43
|
+
text: string;
|
|
44
|
+
}[];
|
|
45
|
+
isError: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
content: {
|
|
48
|
+
type: "text";
|
|
49
|
+
text: string;
|
|
50
|
+
}[];
|
|
51
|
+
isError?: undefined;
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import { PdfService } from '../services/pdfService.js';
|
|
4
|
+
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
5
|
+
export const fillBatchAcroFormSchema = {
|
|
6
|
+
name: 'autoform_fill_batch_acroform',
|
|
7
|
+
description: `Genera MULTIPLES PDFs a partir de un PDF con campos AcroForm (formulario interactivo) + array de datos. Una sola llamada genera N documentos.
|
|
8
|
+
|
|
9
|
+
CUANDO USAR: El PDF base tiene campos AcroForm (detectados con autoform_detect_fields) y el usuario quiere llenarlos con datos de varias personas/filas. Escalable a cientos de documentos en una sola llamada.
|
|
10
|
+
|
|
11
|
+
NO USAR SI: El PDF es estatico (sin AcroForm) → usa autoform_fill_batch_at_coordinates.
|
|
12
|
+
NO USAR SI: Solo necesitas llenar UN documento → usa autoform_fill_pdf.
|
|
13
|
+
|
|
14
|
+
IMPORTANTE sobre nombres de campos:
|
|
15
|
+
- Los campos AcroForm tienen nombres TECNICOS (ej: "Text1", "fld_nombre", "field_001") que NO siempre coinciden con los labels visibles del PDF.
|
|
16
|
+
- DESPUES de llamar autoform_detect_fields, DEBES MIRAR EL PDF VISUALMENTE para correlacionar cada campo tecnico con su label visible segun sus coordenadas.
|
|
17
|
+
- Si las claves de tus data_rows NO coinciden con los nombres tecnicos, usa el parametro field_map para traducir: { "nombre_logico": "nombre_tecnico" }
|
|
18
|
+
- Ejemplo: si el campo tecnico es "Text1" pero visualmente corresponde al label "Nombre:", pasa field_map={"nombre": "Text1"} y data_rows=[{"nombre": "Juan"}]
|
|
19
|
+
|
|
20
|
+
MERGE: Si merge_into_single=true, se genera SOLO el PDF unificado (los individuales son temporales y se borran automaticamente).
|
|
21
|
+
|
|
22
|
+
CODIFICACION: Los datos se escriben EXACTAMENTE como los envias. Acentos espanoles (a e i o u n N u con tildes) estan completamente soportados. NO simplifiques caracteres.`,
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
pdf_path: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Ruta absoluta al PDF con AcroForm'
|
|
29
|
+
},
|
|
30
|
+
data_rows: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
description: 'Array de objetos, cada objeto es un documento. Claves = nombre del campo (tecnico o logico si usas field_map), valores = texto a escribir.',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
additionalProperties: { type: 'string' }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
field_map: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
description: '(Opcional) Mapeo de nombres logicos a nombres tecnicos de AcroForm. Formato: {"nombre_logico": "nombre_tecnico"}. Usalo cuando las claves de data_rows son mas legibles que los nombres tecnicos reales del PDF.',
|
|
41
|
+
additionalProperties: { type: 'string' }
|
|
42
|
+
},
|
|
43
|
+
merge_into_single: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
description: '(Opcional) Si true, une todos los PDFs en uno solo y elimina los individuales. Default: false.'
|
|
46
|
+
},
|
|
47
|
+
output_dir: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: '(Opcional) Directorio de salida. Default: ~/.autoform-mcp/output/'
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
required: ['pdf_path', 'data_rows']
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export async function handleFillBatchAcroForm(args) {
|
|
56
|
+
const { pdf_path, output_dir, merge_into_single = false } = args;
|
|
57
|
+
let data_rows = args.data_rows;
|
|
58
|
+
if (typeof data_rows === 'string') {
|
|
59
|
+
try {
|
|
60
|
+
data_rows = JSON.parse(data_rows);
|
|
61
|
+
}
|
|
62
|
+
catch { /* keep */ }
|
|
63
|
+
}
|
|
64
|
+
let field_map = args.field_map;
|
|
65
|
+
if (typeof field_map === 'string') {
|
|
66
|
+
try {
|
|
67
|
+
field_map = JSON.parse(field_map);
|
|
68
|
+
}
|
|
69
|
+
catch { /* keep */ }
|
|
70
|
+
}
|
|
71
|
+
if (!data_rows || !Array.isArray(data_rows) || data_rows.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: 'text', text: JSON.stringify({ error: true, message: 'data_rows es requerido (array de objetos con datos)' }, null, 2) }],
|
|
74
|
+
isError: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const outDir = output_dir || OUTPUT_DIR;
|
|
78
|
+
const baseName = path.basename(pdf_path, '.pdf').replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
79
|
+
const result = await PdfService.batchFillAcroForm(pdf_path, data_rows, outDir, baseName, merge_into_single, field_map);
|
|
80
|
+
return {
|
|
81
|
+
content: [{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: JSON.stringify({
|
|
84
|
+
documents_generated: merge_into_single ? data_rows.length - result.errors.length : result.outputPaths.length,
|
|
85
|
+
output_paths: result.outputPaths,
|
|
86
|
+
merged_path: result.mergedPath,
|
|
87
|
+
errors: result.errors,
|
|
88
|
+
message: result.mergedPath
|
|
89
|
+
? `Generados ${data_rows.length - result.errors.length} documentos unidos en: ${result.mergedPath}`
|
|
90
|
+
: result.outputPaths.length > 0
|
|
91
|
+
? `Generados ${result.outputPaths.length} documentos en ${outDir}`
|
|
92
|
+
: `Error: ${result.errors.join(', ')}`
|
|
93
|
+
}, null, 2)
|
|
94
|
+
}]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -8,6 +8,13 @@ export const fillBatchAtCoordinatesSchema = {
|
|
|
8
8
|
|
|
9
9
|
CUANDO USAR: El usuario pasa un PDF (certificado, diploma, constancia) y una LISTA de datos (nombres, fechas, etc.) y quiere generar un PDF por cada fila de datos.
|
|
10
10
|
|
|
11
|
+
IMPORTANTE — Codificación de texto:
|
|
12
|
+
- Los datos se escriben EXACTAMENTE como los envías. NO simplifiques ni modifiques caracteres.
|
|
13
|
+
- Acentos españoles (á é í ó ú ñ Ñ ü) están soportados completamente.
|
|
14
|
+
- Nombres como "Sofía", "Benítez", "Peña" deben pasarse con sus tildes intactas.
|
|
15
|
+
|
|
16
|
+
MERGE: Si merge_into_single=true, se genera SOLO el PDF unificado (los individuales son temporales y se borran automáticamente).
|
|
17
|
+
|
|
11
18
|
FLUJO:
|
|
12
19
|
1. Usa autoform_get_pdf_info para obtener las dimensiones de la pagina
|
|
13
20
|
2. Analiza visualmente el PDF para determinar donde van los campos
|
package/dist/tools/fillPdf.js
CHANGED
|
@@ -5,11 +5,16 @@ import { TemplateStore } from '../services/templateStore.js';
|
|
|
5
5
|
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
6
6
|
export const fillPdfSchema = {
|
|
7
7
|
name: 'autoform_fill_pdf',
|
|
8
|
-
description: `Llena UN PDF con valores. Detecta automaticamente si tiene AcroForm o usa un template guardado.
|
|
8
|
+
description: `Llena UN SOLO PDF con valores. Detecta automaticamente si tiene AcroForm o usa un template guardado.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
⚠️ ESTA TOOL ES PARA UN UNICO DOCUMENTO. Si necesitas generar 2 o mas documentos, USA UNA TOOL BATCH (mas eficiente, menos llamadas):
|
|
11
|
+
- PDF con AcroForm + multiples filas → autoform_fill_batch_acroform
|
|
12
|
+
- PDF estatico + multiples filas → autoform_fill_batch_at_coordinates
|
|
13
|
+
- Template guardado + multiples filas → autoform_generate_batch
|
|
14
|
+
|
|
15
|
+
CUANDO USAR: Solo cuando el usuario pide llenar UN documento especifico.
|
|
16
|
+
NO USAR PARA BATCH: Nunca llames esta tool en loop para generar multiples PDFs — usa las tools batch que existen para eso.
|
|
17
|
+
NO USAR SI: El PDF es estatico sin template → usa autoform_fill_at_coordinates.`,
|
|
13
18
|
inputSchema: {
|
|
14
19
|
type: 'object',
|
|
15
20
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autoform-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "MCP server for bulk PDF form filling. Detect fields, fill templates, and generate hundreds of PDFs from data — directly from Claude.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|