autoform-mcp-server 1.6.0 → 1.6.1
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 +2 -2
- package/dist/services/pdfTextExtractor.d.ts +31 -3
- package/dist/services/pdfTextExtractor.js +178 -0
- package/dist/tools/analyzeStaticPdf.js +17 -10
- package/dist/tools/fillAtCoordinates.js +14 -21
- package/dist/tools/fillBatchAcroForm.js +9 -5
- package/dist/tools/fillBatchAtCoordinates.js +22 -10
- package/dist/tools/fillPdf.js +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { handleGenerateBatch, generateBatchSchema } from './tools/generateBatch.
|
|
|
13
13
|
import { handleListTemplates, listTemplatesSchema } from './tools/manageTemplates.js';
|
|
14
14
|
import { handleImportTemplate, importTemplateSchema } from './tools/importTemplate.js';
|
|
15
15
|
import { withMetrics } from './services/metricsLogger.js';
|
|
16
|
-
const server = new Server({ name: 'autoform', version: '1.6.
|
|
16
|
+
const server = new Server({ name: 'autoform', version: '1.6.1' }, { capabilities: { tools: {} } });
|
|
17
17
|
// Register all tools
|
|
18
18
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
19
19
|
tools: [
|
|
@@ -78,4 +78,4 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
78
78
|
// Start server
|
|
79
79
|
const transport = new StdioServerTransport();
|
|
80
80
|
await server.connect(transport);
|
|
81
|
-
console.error('[AutoForm MCP] Server v1.6.
|
|
81
|
+
console.error('[AutoForm MCP] Server v1.6.1 started — 11 tools registered (metrics enabled)');
|
|
@@ -11,16 +11,31 @@ export interface TextItem {
|
|
|
11
11
|
page: number;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* A graphic element detected from PDF drawing operators.
|
|
15
15
|
*/
|
|
16
|
-
export interface
|
|
16
|
+
export interface GraphicElement {
|
|
17
|
+
type: 'checkbox' | 'field_box' | 'horizontal_line' | 'rectangle';
|
|
17
18
|
x: number;
|
|
18
19
|
y: number;
|
|
19
20
|
width: number;
|
|
21
|
+
height: number;
|
|
20
22
|
page: number;
|
|
23
|
+
nearbyLabel?: string;
|
|
24
|
+
labelPosition?: 'left' | 'right' | 'above' | 'below';
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
|
-
*
|
|
27
|
+
* Complete page information including text AND graphic elements.
|
|
28
|
+
*/
|
|
29
|
+
export interface PageFullInfo {
|
|
30
|
+
page: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
textItems: TextItem[];
|
|
34
|
+
graphicElements: GraphicElement[];
|
|
35
|
+
totalCharacters: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A page's extracted information (text only, for backward compatibility).
|
|
24
39
|
*/
|
|
25
40
|
export interface PageTextInfo {
|
|
26
41
|
page: number;
|
|
@@ -40,6 +55,19 @@ export type PdfContentType = 'acroform' | 'text_vectorial' | 'image_only' | 'mix
|
|
|
40
55
|
export declare class PdfTextExtractor {
|
|
41
56
|
/** Extract all text items from all pages of a PDF */
|
|
42
57
|
static extractAllText(pdfPath: string): Promise<PageTextInfo[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Extract ALL information from a PDF: text items + graphic elements (rectangles, lines).
|
|
60
|
+
* Uses both getTextContent() and getOperatorList() to build a complete map of the page.
|
|
61
|
+
*
|
|
62
|
+
* Graphic elements are classified as:
|
|
63
|
+
* - checkbox: small square (6-16pt per side), likely a check box
|
|
64
|
+
* - field_box: medium/large rectangle (width > 40pt), likely an input field area
|
|
65
|
+
* - horizontal_line: very thin rectangle (height < 2pt, width > 30pt), likely an underline for writing
|
|
66
|
+
* - rectangle: anything else
|
|
67
|
+
*
|
|
68
|
+
* Each graphic element gets a nearbyLabel from the closest text item.
|
|
69
|
+
*/
|
|
70
|
+
static extractFullPageInfo(pdfPath: string): Promise<PageFullInfo[]>;
|
|
43
71
|
/**
|
|
44
72
|
* Classify a PDF based on extractable text density.
|
|
45
73
|
* Thresholds are empirical; can be tuned.
|
|
@@ -55,6 +55,184 @@ export class PdfTextExtractor {
|
|
|
55
55
|
}
|
|
56
56
|
return pages;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract ALL information from a PDF: text items + graphic elements (rectangles, lines).
|
|
60
|
+
* Uses both getTextContent() and getOperatorList() to build a complete map of the page.
|
|
61
|
+
*
|
|
62
|
+
* Graphic elements are classified as:
|
|
63
|
+
* - checkbox: small square (6-16pt per side), likely a check box
|
|
64
|
+
* - field_box: medium/large rectangle (width > 40pt), likely an input field area
|
|
65
|
+
* - horizontal_line: very thin rectangle (height < 2pt, width > 30pt), likely an underline for writing
|
|
66
|
+
* - rectangle: anything else
|
|
67
|
+
*
|
|
68
|
+
* Each graphic element gets a nearbyLabel from the closest text item.
|
|
69
|
+
*/
|
|
70
|
+
static async extractFullPageInfo(pdfPath) {
|
|
71
|
+
const bytes = fs.readFileSync(pdfPath);
|
|
72
|
+
const pdf = await pdfjsLib.getDocument({
|
|
73
|
+
data: new Uint8Array(bytes),
|
|
74
|
+
useSystemFonts: true,
|
|
75
|
+
verbosity: 0
|
|
76
|
+
}).promise;
|
|
77
|
+
const results = [];
|
|
78
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
79
|
+
const page = await pdf.getPage(pageNum);
|
|
80
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
81
|
+
// 1. Extract text (same as extractAllText)
|
|
82
|
+
const textContent = await page.getTextContent();
|
|
83
|
+
const textItems = [];
|
|
84
|
+
let totalChars = 0;
|
|
85
|
+
for (const raw of textContent.items) {
|
|
86
|
+
const text = String(raw.str ?? '').trim();
|
|
87
|
+
if (text === '')
|
|
88
|
+
continue;
|
|
89
|
+
const [, , , scaleY, tx, ty] = raw.transform;
|
|
90
|
+
const fontSize = Math.abs(scaleY);
|
|
91
|
+
const width = Math.abs(raw.width ?? 0);
|
|
92
|
+
const height = Math.abs(raw.height ?? fontSize);
|
|
93
|
+
textItems.push({ text, x: tx, y: ty, width, height, fontSize, page: pageNum });
|
|
94
|
+
totalChars += text.length;
|
|
95
|
+
}
|
|
96
|
+
// 2. Extract graphic elements from operator list
|
|
97
|
+
const operatorList = await page.getOperatorList();
|
|
98
|
+
const graphicElements = [];
|
|
99
|
+
// pdfjs OPS constants
|
|
100
|
+
const OPS = pdfjsLib.OPS;
|
|
101
|
+
// Track current transform matrix for coordinate conversion
|
|
102
|
+
// The operator list contains transform operations + rectangle draws
|
|
103
|
+
let currentTransform = [1, 0, 0, 1, 0, 0]; // identity
|
|
104
|
+
for (let i = 0; i < operatorList.fnArray.length; i++) {
|
|
105
|
+
const fn = operatorList.fnArray[i];
|
|
106
|
+
const args = operatorList.argsArray[i];
|
|
107
|
+
// Track transform changes
|
|
108
|
+
if (fn === OPS.transform) {
|
|
109
|
+
if (args && args.length >= 6) {
|
|
110
|
+
currentTransform = args;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Detect rectangle operations: constructPath contains 'rectangle' ops
|
|
114
|
+
if (fn === OPS.constructPath) {
|
|
115
|
+
const ops = args[0]; // array of sub-operations
|
|
116
|
+
const pathArgs = args[1]; // array of coordinates [x, y, w, h, ...]
|
|
117
|
+
if (!ops || !pathArgs)
|
|
118
|
+
continue;
|
|
119
|
+
let argIdx = 0;
|
|
120
|
+
for (const op of ops) {
|
|
121
|
+
// op 13 = rectangle in pdfjs (OPS.rectangle)
|
|
122
|
+
if (op === 13 && argIdx + 3 < pathArgs.length) {
|
|
123
|
+
const rx = pathArgs[argIdx];
|
|
124
|
+
const ry = pathArgs[argIdx + 1];
|
|
125
|
+
const rw = pathArgs[argIdx + 2];
|
|
126
|
+
const rh = pathArgs[argIdx + 3];
|
|
127
|
+
// Apply current transform to get absolute coordinates
|
|
128
|
+
const absX = currentTransform[4] + rx * currentTransform[0];
|
|
129
|
+
const absY = currentTransform[5] + ry * currentTransform[3];
|
|
130
|
+
const absW = Math.abs(rw * currentTransform[0]);
|
|
131
|
+
const absH = Math.abs(rh * currentTransform[3]);
|
|
132
|
+
// Filter: skip tiny rects (artifacts) and page-sized rects (backgrounds)
|
|
133
|
+
if (absW < 4 || absH < 1) {
|
|
134
|
+
argIdx += 4;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (absW > viewport.width * 0.95 && absH > viewport.height * 0.95) {
|
|
138
|
+
argIdx += 4;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Classify the rectangle
|
|
142
|
+
let type;
|
|
143
|
+
if (absH < 2.5 && absW > 30) {
|
|
144
|
+
type = 'horizontal_line';
|
|
145
|
+
}
|
|
146
|
+
else if (absW >= 6 && absW <= 25 && absH >= 6 && absH <= 25 && Math.abs(absW - absH) < 6) {
|
|
147
|
+
type = 'checkbox';
|
|
148
|
+
}
|
|
149
|
+
else if (absW > 50 && absH > 10 && absH < 40) {
|
|
150
|
+
type = 'field_box';
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
type = 'rectangle';
|
|
154
|
+
}
|
|
155
|
+
graphicElements.push({
|
|
156
|
+
type,
|
|
157
|
+
x: Math.round(absX * 100) / 100,
|
|
158
|
+
y: Math.round(absY * 100) / 100,
|
|
159
|
+
width: Math.round(absW * 100) / 100,
|
|
160
|
+
height: Math.round(absH * 100) / 100,
|
|
161
|
+
page: pageNum
|
|
162
|
+
});
|
|
163
|
+
argIdx += 4;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Other path ops: moveTo(2 args), lineTo(2 args), etc.
|
|
167
|
+
if (op === 14 || op === 15)
|
|
168
|
+
argIdx += 2; // moveTo, lineTo
|
|
169
|
+
else if (op === 16)
|
|
170
|
+
argIdx += 6; // bezierCurveTo
|
|
171
|
+
else if (op === 17)
|
|
172
|
+
argIdx += 0; // closePath
|
|
173
|
+
else
|
|
174
|
+
argIdx += 2; // safe default
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// 2.5. Deduplicate overlapping graphic elements of the same type
|
|
180
|
+
// PDFs often have multiple border layers for the same visual element
|
|
181
|
+
const deduped = [];
|
|
182
|
+
for (const elem of graphicElements) {
|
|
183
|
+
const isDuplicate = deduped.some(existing => existing.type === elem.type &&
|
|
184
|
+
existing.page === elem.page &&
|
|
185
|
+
Math.abs(existing.x - elem.x) < 8 &&
|
|
186
|
+
Math.abs(existing.y - elem.y) < 8 &&
|
|
187
|
+
Math.abs(existing.width - elem.width) < 10 &&
|
|
188
|
+
Math.abs(existing.height - elem.height) < 10);
|
|
189
|
+
if (!isDuplicate)
|
|
190
|
+
deduped.push(elem);
|
|
191
|
+
}
|
|
192
|
+
graphicElements.length = 0;
|
|
193
|
+
graphicElements.push(...deduped);
|
|
194
|
+
// 3. Correlate each graphic element with the nearest text label
|
|
195
|
+
for (const elem of graphicElements) {
|
|
196
|
+
const elemCenterX = elem.x + elem.width / 2;
|
|
197
|
+
const elemCenterY = elem.y + elem.height / 2;
|
|
198
|
+
let bestLabel = '';
|
|
199
|
+
let bestDist = Infinity;
|
|
200
|
+
let bestPos = 'left';
|
|
201
|
+
for (const ti of textItems) {
|
|
202
|
+
const textCenterX = ti.x + ti.width / 2;
|
|
203
|
+
const textCenterY = ti.y + ti.height / 2;
|
|
204
|
+
const dx = elemCenterX - textCenterX;
|
|
205
|
+
const dy = elemCenterY - textCenterY;
|
|
206
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
207
|
+
if (dist < bestDist && dist < 200) { // max 200pt search radius
|
|
208
|
+
bestDist = dist;
|
|
209
|
+
bestLabel = ti.text;
|
|
210
|
+
const absDx = Math.abs(dx);
|
|
211
|
+
const absDy = Math.abs(dy);
|
|
212
|
+
if (absDx > absDy) {
|
|
213
|
+
bestPos = dx > 0 ? 'left' : 'right';
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
bestPos = dy > 0 ? 'below' : 'above';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (bestLabel) {
|
|
221
|
+
elem.nearbyLabel = bestLabel;
|
|
222
|
+
elem.labelPosition = bestPos;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
results.push({
|
|
226
|
+
page: pageNum,
|
|
227
|
+
width: viewport.width,
|
|
228
|
+
height: viewport.height,
|
|
229
|
+
textItems,
|
|
230
|
+
graphicElements,
|
|
231
|
+
totalCharacters: totalChars
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return results;
|
|
235
|
+
}
|
|
58
236
|
/**
|
|
59
237
|
* Classify a PDF based on extractable text density.
|
|
60
238
|
* Thresholds are empirical; can be tuned.
|
|
@@ -21,11 +21,18 @@ Si recibes este tipo, NO intentes procesar el PDF con los otros tools. En su lug
|
|
|
21
21
|
3. Proporcionar coordenadas manualmente si ya las conoces
|
|
22
22
|
|
|
23
23
|
⚠️ CASO text_vectorial:
|
|
24
|
-
- suggested_fields son SUGERENCIAS heuristicas
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
|
|
24
|
+
- suggested_fields son SUGERENCIAS heuristicas que pueden estar en posiciones incorrectas para layouts atipicos (centrados, tabulares complejos).
|
|
25
|
+
- USA all_text_items para razonar sobre el layout real del PDF.
|
|
26
|
+
- Descarta sugerencias que son texto decorativo ("CERTIFICADO", "Firma 1/2").
|
|
27
|
+
- Cuando no estes seguro del layout, PREGUNTA al usuario o pidele que adjunte el PDF al chat para verlo visualmente.
|
|
28
|
+
|
|
29
|
+
⚠️ FORMULARIOS COMPLEJOS:
|
|
30
|
+
- Si el formulario tiene muchos campos (>10), tablas densas, o un layout no obvio, RECOMIENDA al usuario adjuntar el PDF al chat para verificacion visual antes de generar. Dile algo como: "Este formulario es complejo. Para asegurar precision, te recomiendo adjuntarme el PDF al chat para que pueda verificar las posiciones visualmente."
|
|
31
|
+
- Si el usuario ya adjunto el PDF al chat Y tambien te dio la ruta, APROVECHA ambos: usa la imagen visual para verificar que tus coordenadas son correctas.
|
|
32
|
+
|
|
33
|
+
⚠️ CHECKBOXES:
|
|
34
|
+
- Si detectas textos tipo opciones multiples en la misma linea Y (ej: "ALUMNO", "DOCENTE", "ADMINISTRATIVO"), es probable que haya casillas de verificacion.
|
|
35
|
+
- NO adivines la posicion de los cuadrados. Pide al usuario que adjunte el PDF para verlo visualmente.
|
|
29
36
|
|
|
30
37
|
⚠️ CASO mixed:
|
|
31
38
|
Hay algo de texto pero poca densidad. Puede haber campos que NO se detectaron automaticamente porque el label es una imagen. Usa suggested_fields como punto de partida pero verifica visualmente si faltan campos.`,
|
|
@@ -127,7 +134,7 @@ export async function handleAnalyzeStaticPdf(args) {
|
|
|
127
134
|
})));
|
|
128
135
|
const summary = suggestedFields.length > 0
|
|
129
136
|
? `Se detectaron ${suggestedFields.length} campos candidatos automaticamente:\n${suggestedFields.map(f => `- "${f.inferredLabel}" en pag ${f.page}, pos (${f.suggestedX}, ${f.suggestedY}), ${f.suggestedWidth}x${f.suggestedHeight} [${f.confidence}]`).join('\n')}`
|
|
130
|
-
: 'No se detectaron campos candidatos automaticamente. El PDF tiene texto pero no hay patrones claros de "Label: ___" reconocibles.';
|
|
137
|
+
: 'No se detectaron campos candidatos automaticamente. El PDF tiene texto pero no hay patrones claros de "Label: ___" reconocibles. Revisa all_text_items para ver todo el texto del PDF con posiciones y razona sobre el layout.';
|
|
131
138
|
return {
|
|
132
139
|
content: [{
|
|
133
140
|
type: 'text',
|
|
@@ -143,14 +150,14 @@ export async function handleAnalyzeStaticPdf(args) {
|
|
|
143
150
|
},
|
|
144
151
|
total_text_items: allTextItems.length,
|
|
145
152
|
suggested_fields: suggestedFields,
|
|
146
|
-
all_text_items: allTextItems.slice(0, 100),
|
|
153
|
+
all_text_items: allTextItems.slice(0, 100),
|
|
147
154
|
text_items_truncated: allTextItems.length > 100,
|
|
148
155
|
summary,
|
|
149
156
|
guidance: contentType === 'mixed'
|
|
150
|
-
? 'El PDF tiene densidad baja de texto
|
|
157
|
+
? 'El PDF tiene densidad baja de texto. Usa all_text_items como referencia y pregunta al usuario si faltan campos.'
|
|
151
158
|
: suggestedFields.length > 0
|
|
152
|
-
? 'Usa los suggested_fields
|
|
153
|
-
: 'No hay campos obvios detectables.
|
|
159
|
+
? 'Usa los suggested_fields como punto de partida. IMPORTANTE SOBRE CHECKBOXES: si detectas en all_text_items textos que parecen opciones multiples en la misma linea (ej: "ALUMNO", "DOCENTE", "ADMINISTRATIVO" con coordenadas Y similares), es MUY probable que el formulario tenga casillas de verificacion. En ese caso, NO adivines la posicion de los cuadrados — pide al usuario que adjunte el PDF al chat para que puedas verlo visualmente y determinar la posicion exacta de cada casilla antes de marcar con "X".'
|
|
160
|
+
: 'No hay campos obvios detectables por heuristicas. IMPORTANTE: si en all_text_items detectas textos que parecen opciones multiples (ej: varias opciones en la misma linea Y), probablemente hay casillas de verificacion cuya posicion exacta no puedo determinar solo con texto. Pide al usuario que adjunte el PDF al chat para verlo visualmente, o que te indique donde estan las casillas.'
|
|
154
161
|
}, null, 2)
|
|
155
162
|
}]
|
|
156
163
|
};
|
|
@@ -1,31 +1,24 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import * as os from 'os';
|
|
3
2
|
import { PdfService } from '../services/pdfService.js';
|
|
4
|
-
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
5
3
|
export const fillAtCoordinatesSchema = {
|
|
6
4
|
name: 'autoform_fill_at_coordinates',
|
|
7
|
-
description: `Escribe texto en posiciones exactas de UN PDF
|
|
8
|
-
Ideal cuando Claude analiza visualmente un PDF y determina dónde colocar el texto.
|
|
5
|
+
description: `Escribe texto en posiciones exactas de UN PDF.
|
|
9
6
|
|
|
10
|
-
⚠️
|
|
7
|
+
⚠️ PARA MULTIPLES DOCUMENTOS usa autoform_fill_batch_at_coordinates (una sola llamada, mas eficiente).
|
|
11
8
|
|
|
12
|
-
|
|
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.
|
|
9
|
+
CODIFICACION: Acentos (á é í ó ú ñ Ñ ü) soportados. NO simplifiques caracteres.
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
1. Primero usa autoform_get_pdf_info para obtener las dimensiones de cada página
|
|
20
|
-
2. Basándote en el análisis visual del PDF, calcula las coordenadas en puntos PDF
|
|
21
|
-
3. Recuerda: Y=0 está ABAJO, Y aumenta hacia ARRIBA
|
|
11
|
+
CHECKBOXES: Solo marca casillas si puedes VER el PDF visualmente. Si no, pide al usuario que lo adjunte al chat.
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
VERIFICACION VISUAL: Si el usuario adjunto el PDF Y dio la ruta, aprovecha la imagen para verificar coordenadas antes de escribir.
|
|
14
|
+
|
|
15
|
+
DIRECTORIO DE SALIDA: Si no se especifica output_path, guarda en el MISMO directorio del pdf_path original.
|
|
16
|
+
|
|
17
|
+
NO GENERES PDF DE PRUEBA: Genera el resultado final directamente. No hagas "tests" individuales a menos que el usuario lo pida.
|
|
18
|
+
|
|
19
|
+
CUANDO TENGAS DUDAS: Si el formulario es complejo y no estas seguro de las posiciones, PREGUNTA al usuario o pidele que adjunte el PDF al chat. Mejor preguntar que generar mal.
|
|
20
|
+
|
|
21
|
+
COORDENADAS: Origen (0,0) = esquina inferior-izquierda. Y aumenta hacia arriba. Puntos PDF (A4 ≈ 595x842, Letter ≈ 612x792).`,
|
|
29
22
|
inputSchema: {
|
|
30
23
|
type: 'object',
|
|
31
24
|
properties: {
|
|
@@ -89,7 +82,7 @@ export async function handleFillAtCoordinates(args) {
|
|
|
89
82
|
isError: true
|
|
90
83
|
};
|
|
91
84
|
}
|
|
92
|
-
const outputFile = output_path || path.join(
|
|
85
|
+
const outputFile = output_path || path.join(path.dirname(pdf_path), `${path.basename(pdf_path, '.pdf')}_filled_${Date.now()}.pdf`);
|
|
93
86
|
const filled = await PdfService.fillAtCoordinates(pdf_path, fields, outputFile);
|
|
94
87
|
return {
|
|
95
88
|
content: [{
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import * as os from 'os';
|
|
3
2
|
import { PdfService } from '../services/pdfService.js';
|
|
4
|
-
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
5
3
|
export const fillBatchAcroFormSchema = {
|
|
6
4
|
name: 'autoform_fill_batch_acroform',
|
|
7
5
|
description: `Genera MULTIPLES PDFs a partir de un PDF con campos AcroForm (formulario interactivo) + array de datos. Una sola llamada genera N documentos.
|
|
@@ -17,9 +15,15 @@ IMPORTANTE sobre nombres de campos:
|
|
|
17
15
|
- 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
16
|
- Ejemplo: si el campo tecnico es "Text1" pero visualmente corresponde al label "Nombre:", pasa field_map={"nombre": "Text1"} y data_rows=[{"nombre": "Juan"}]
|
|
19
17
|
|
|
20
|
-
MERGE: Si merge_into_single=true, se genera SOLO el PDF unificado
|
|
18
|
+
MERGE: Si merge_into_single=true, se genera SOLO el PDF unificado.
|
|
21
19
|
|
|
22
|
-
CODIFICACION:
|
|
20
|
+
CODIFICACION: Acentos (á é í ó ú ñ) soportados completamente. NO simplifiques caracteres.
|
|
21
|
+
|
|
22
|
+
DIRECTORIO DE SALIDA: Si no se especifica output_dir, usa el MISMO directorio donde esta el pdf_path original.
|
|
23
|
+
|
|
24
|
+
NO GENERES PDF DE PRUEBA: Genera el batch completo directamente. No hagas un "test" individual primero a menos que el usuario lo pida.
|
|
25
|
+
|
|
26
|
+
VERIFICACION: Si el usuario adjunto el PDF al chat Y dio la ruta, aprovecha la imagen visual para verificar que los inferred_labels son correctos.`,
|
|
23
27
|
inputSchema: {
|
|
24
28
|
type: 'object',
|
|
25
29
|
properties: {
|
|
@@ -74,7 +78,7 @@ export async function handleFillBatchAcroForm(args) {
|
|
|
74
78
|
isError: true
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
|
-
const outDir = output_dir ||
|
|
81
|
+
const outDir = output_dir || path.dirname(pdf_path);
|
|
78
82
|
const baseName = path.basename(pdf_path, '.pdf').replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
79
83
|
const result = await PdfService.batchFillAcroForm(pdf_path, data_rows, outDir, baseName, merge_into_single, field_map);
|
|
80
84
|
return {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import * as os from 'os';
|
|
3
2
|
import { PdfService } from '../services/pdfService.js';
|
|
4
|
-
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
5
3
|
export const fillBatchAtCoordinatesSchema = {
|
|
6
4
|
name: 'autoform_fill_batch_at_coordinates',
|
|
7
5
|
description: `Genera MULTIPLES PDFs a partir de un mismo PDF base, escribiendo datos diferentes en las mismas posiciones de cada copia.
|
|
@@ -11,16 +9,30 @@ CUANDO USAR: El usuario pasa un PDF (certificado, diploma, constancia) y una LIS
|
|
|
11
9
|
IMPORTANTE — Codificación de texto:
|
|
12
10
|
- Los datos se escriben EXACTAMENTE como los envías. NO simplifiques ni modifiques caracteres.
|
|
13
11
|
- Acentos españoles (á é í ó ú ñ Ñ ü) están soportados completamente.
|
|
14
|
-
|
|
12
|
+
|
|
13
|
+
IMPORTANTE — Casillas de verificación (checkboxes) en PDFs estaticos:
|
|
14
|
+
- Si detectas opciones tipo checkbox, SOLO marcalas si puedes VER el PDF visualmente (adjuntado al chat). Si no puedes verlo, pide al usuario que lo adjunte.
|
|
15
|
+
- Para marcar: text="X", fontSize=10, en las coordenadas exactas del cuadrado.
|
|
16
|
+
|
|
17
|
+
IMPORTANTE — Verificación visual:
|
|
18
|
+
- Si el usuario adjunto el PDF al chat Y tambien dio la ruta, APROVECHA la imagen visual para verificar que tus coordenadas son correctas antes de generar.
|
|
19
|
+
- Si tienes dudas sobre el layout de un formulario complejo (tablas densas, campos no claros), PREGUNTA al usuario antes de generar. Es mejor preguntar que generar mal.
|
|
20
|
+
|
|
21
|
+
DIRECTORIO DE SALIDA POR DEFECTO:
|
|
22
|
+
- Si el usuario NO especifica output_dir, usa el MISMO directorio donde esta el PDF base (pdf_path). No uses ~/.autoform-mcp/output/ por defecto — el usuario espera los archivos cerca de su PDF original.
|
|
23
|
+
|
|
24
|
+
NO GENERES PDFs DE PRUEBA/TEST:
|
|
25
|
+
- Genera directamente el batch completo. NO generes un "test" individual primero a menos que el usuario lo pida explicitamente. Generar tests innecesarios desperdicia tiempo y confunde al usuario con archivos extra.
|
|
15
26
|
|
|
16
27
|
MERGE: Si merge_into_single=true, se genera SOLO el PDF unificado (los individuales son temporales y se borran automáticamente).
|
|
17
28
|
|
|
18
|
-
FLUJO:
|
|
19
|
-
1. Usa autoform_get_pdf_info para
|
|
20
|
-
2.
|
|
21
|
-
3.
|
|
22
|
-
4.
|
|
23
|
-
5.
|
|
29
|
+
FLUJO CORRECTO:
|
|
30
|
+
1. Usa autoform_get_pdf_info para dimensiones
|
|
31
|
+
2. Si puedes ver el PDF (adjuntado al chat): analiza visualmente donde van los campos
|
|
32
|
+
3. Si NO puedes verlo: usa autoform_analyze_static_pdf para obtener texto+posiciones, y si detectas posibles checkboxes o layout complejo, pide al usuario que adjunte el PDF
|
|
33
|
+
4. Define los campos con coordenadas
|
|
34
|
+
5. Genera el batch completo de una vez (NO hagas un test primero)
|
|
35
|
+
6. Informa al usuario la ruta del resultado
|
|
24
36
|
|
|
25
37
|
SISTEMA DE COORDENADAS: Origen (0,0) = esquina inferior-izquierda. Y aumenta hacia arriba. Unidades: puntos PDF.`,
|
|
26
38
|
inputSchema: {
|
|
@@ -90,7 +102,7 @@ export async function handleFillBatchAtCoordinates(args) {
|
|
|
90
102
|
if (!data_rows || !Array.isArray(data_rows) || data_rows.length === 0) {
|
|
91
103
|
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message: 'data_rows es requerido (array de objetos con datos)' }, null, 2) }], isError: true };
|
|
92
104
|
}
|
|
93
|
-
const outDir = output_dir ||
|
|
105
|
+
const outDir = output_dir || path.dirname(pdf_path);
|
|
94
106
|
const baseName = path.basename(pdf_path, '.pdf').replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
95
107
|
const result = await PdfService.batchFillAtCoordinates(pdf_path, field_definitions, data_rows, outDir, baseName, merge_into_single);
|
|
96
108
|
return {
|
package/dist/tools/fillPdf.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import * as os from 'os';
|
|
3
2
|
import { PdfService } from '../services/pdfService.js';
|
|
4
3
|
import { TemplateStore } from '../services/templateStore.js';
|
|
5
|
-
const OUTPUT_DIR = path.join(os.homedir(), '.autoform-mcp', 'output');
|
|
6
4
|
export const fillPdfSchema = {
|
|
7
5
|
name: 'autoform_fill_pdf',
|
|
8
6
|
description: `Llena UN SOLO PDF con valores. Detecta automaticamente si tiene AcroForm o usa un template guardado.
|
|
@@ -13,8 +11,10 @@ export const fillPdfSchema = {
|
|
|
13
11
|
- Template guardado + multiples filas → autoform_generate_batch
|
|
14
12
|
|
|
15
13
|
CUANDO USAR: Solo cuando el usuario pide llenar UN documento especifico.
|
|
16
|
-
NO USAR PARA BATCH: Nunca llames esta tool en loop
|
|
17
|
-
NO USAR SI: El PDF es estatico sin template → usa autoform_fill_at_coordinates
|
|
14
|
+
NO USAR PARA BATCH: Nunca llames esta tool en loop — usa las tools batch.
|
|
15
|
+
NO USAR SI: El PDF es estatico sin template → usa autoform_fill_at_coordinates.
|
|
16
|
+
|
|
17
|
+
DIRECTORIO DE SALIDA: Si no se especifica output_path, guarda en el MISMO directorio del pdf_path original.`,
|
|
18
18
|
inputSchema: {
|
|
19
19
|
type: 'object',
|
|
20
20
|
properties: {
|
|
@@ -48,7 +48,7 @@ export async function handleFillPdf(args) {
|
|
|
48
48
|
}
|
|
49
49
|
catch { /* keep as-is */ }
|
|
50
50
|
}
|
|
51
|
-
const outputFile = output_path || path.join(
|
|
51
|
+
const outputFile = output_path || path.join(path.dirname(pdf_path), `${path.basename(pdf_path, '.pdf')}_filled_${Date.now()}.pdf`);
|
|
52
52
|
// Try AcroForm first
|
|
53
53
|
const { fields, hasAcroform } = await PdfService.detectFields(pdf_path);
|
|
54
54
|
if (hasAcroform && fields.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autoform-mcp-server",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
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",
|