kordoc 3.13.0 → 3.16.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.
Files changed (54) hide show
  1. package/README.md +34 -1
  2. package/dist/{-MRPDPE2X.js → -H7N62NZU.js} +24 -13
  3. package/dist/{chunk-33CIKNL4.js → chunk-5RHRQ7HK.js} +479 -258
  4. package/dist/chunk-5RHRQ7HK.js.map +1 -0
  5. package/dist/{chunk-MEXUFQVC.cjs → chunk-BIP4JAVG.cjs} +5 -5
  6. package/dist/chunk-BIP4JAVG.cjs.map +1 -0
  7. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  8. package/dist/chunk-FGL7BH4P.js +1019 -0
  9. package/dist/chunk-FGL7BH4P.js.map +1 -0
  10. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  11. package/dist/{chunk-Q6UXVJXI.js → chunk-IC56KHBL.js} +2 -2
  12. package/dist/chunk-JFNWJFKP.js +185 -0
  13. package/dist/chunk-JFNWJFKP.js.map +1 -0
  14. package/dist/{chunk-YXOT7P5B.js → chunk-LYL4IL3E.js} +5 -5
  15. package/dist/{chunk-YXOT7P5B.js.map → chunk-LYL4IL3E.js.map} +1 -1
  16. package/dist/{chunk-O4WFPOFN.js → chunk-Q7EGGW3H.js} +5 -5
  17. package/dist/{chunk-O4WFPOFN.js.map → chunk-Q7EGGW3H.js.map} +1 -1
  18. package/dist/{chunk-TWACKS3K.js → chunk-WD5NUUU6.js} +571 -729
  19. package/dist/chunk-WD5NUUU6.js.map +1 -0
  20. package/dist/cli.js +125 -12
  21. package/dist/cli.js.map +1 -1
  22. package/dist/formula-5NKVS2LR.cjs.map +1 -1
  23. package/dist/index.cjs +1668 -449
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +144 -6
  26. package/dist/index.d.ts +144 -6
  27. package/dist/index.js +1502 -283
  28. package/dist/index.js.map +1 -1
  29. package/dist/mcp.js +93 -12
  30. package/dist/mcp.js.map +1 -1
  31. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  32. package/dist/{parser-Z3IL2ESY.js → parser-5G247XYD.js} +54 -6
  33. package/dist/parser-5G247XYD.js.map +1 -0
  34. package/dist/{parser-DZBI3EA2.cjs → parser-6V7F3GET.cjs} +66 -18
  35. package/dist/parser-6V7F3GET.cjs.map +1 -0
  36. package/dist/{parser-XUNGOH76.js → parser-HBHCD7TE.js} +55 -7
  37. package/dist/parser-HBHCD7TE.js.map +1 -0
  38. package/dist/provider-G4C2V2PD.cjs.map +1 -1
  39. package/dist/render-VA4NHROW.js +10 -0
  40. package/dist/seal-UVDRGHEF.js +10 -0
  41. package/dist/seal-UVDRGHEF.js.map +1 -0
  42. package/dist/{watch-G5NFIT5L.js → watch-HIYNOSFD.js} +7 -5
  43. package/dist/{watch-G5NFIT5L.js.map → watch-HIYNOSFD.js.map} +1 -1
  44. package/package.json +4 -3
  45. package/dist/chunk-33CIKNL4.js.map +0 -1
  46. package/dist/chunk-MEXUFQVC.cjs.map +0 -1
  47. package/dist/chunk-TWACKS3K.js.map +0 -1
  48. package/dist/parser-DZBI3EA2.cjs.map +0 -1
  49. package/dist/parser-XUNGOH76.js.map +0 -1
  50. package/dist/parser-Z3IL2ESY.js.map +0 -1
  51. package/dist/render-VZ4OGHZU.js +0 -9
  52. /package/dist/{-MRPDPE2X.js.map → -H7N62NZU.js.map} +0 -0
  53. /package/dist/{chunk-Q6UXVJXI.js.map → chunk-IC56KHBL.js.map} +0 -0
  54. /package/dist/{render-VZ4OGHZU.js.map → render-VA4NHROW.js.map} +0 -0
package/dist/mcp.js CHANGED
@@ -6,26 +6,29 @@ import {
6
6
  extractHwpxMetadataOnly,
7
7
  fillFormFields,
8
8
  fillHwpx,
9
+ fillWithUniqueGuard,
9
10
  markdownToHwpx,
10
11
  parse,
11
12
  patchHwp,
12
13
  patchHwpx
13
- } from "./chunk-TWACKS3K.js";
14
+ } from "./chunk-WD5NUUU6.js";
15
+ import {
16
+ blocksToMarkdown
17
+ } from "./chunk-LYL4IL3E.js";
14
18
  import {
15
19
  detectFormat,
16
20
  detectZipFormat
17
21
  } from "./chunk-MEPHGCPQ.js";
18
- import "./chunk-33CIKNL4.js";
19
- import {
20
- blocksToMarkdown
21
- } from "./chunk-YXOT7P5B.js";
22
22
  import "./chunk-MOL7MDBG.js";
23
+ import "./chunk-FGL7BH4P.js";
24
+ import "./chunk-5RHRQ7HK.js";
25
+ import "./chunk-JFNWJFKP.js";
23
26
  import {
24
27
  KordocError,
25
28
  VERSION,
26
29
  sanitizeError,
27
30
  toArrayBuffer
28
- } from "./chunk-Q6UXVJXI.js";
31
+ } from "./chunk-IC56KHBL.js";
29
32
 
30
33
  // src/mcp.ts
31
34
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -196,7 +199,7 @@ server.tool(
196
199
  break;
197
200
  case "pdf":
198
201
  try {
199
- const { extractPdfMetadataOnly } = await import("./parser-XUNGOH76.js");
202
+ const { extractPdfMetadataOnly } = await import("./parser-HBHCD7TE.js");
200
203
  metadata = await extractPdfMetadataOnly(buffer);
201
204
  } catch {
202
205
  metadata = void 0;
@@ -377,16 +380,27 @@ server.tool(
377
380
  }
378
381
  }
379
382
  );
383
+ function buildFillInputs(fields, formats) {
384
+ const out = {};
385
+ for (const [k, v] of Object.entries(fields)) {
386
+ const format = formats?.[k];
387
+ out[k] = format ? { value: v, format } : v;
388
+ }
389
+ return out;
390
+ }
380
391
  server.tool(
381
392
  "fill_form",
382
393
  "\uD55C\uAD6D \uC11C\uC2DD \uBB38\uC11C\uC758 \uBE48\uCE78\uC744 \uCC44\uC6CC\uC11C \uC0C8 \uBB38\uC11C\uB85C \uCD9C\uB825\uD569\uB2C8\uB2E4. hwpx-preserve\uB97C \uC0AC\uC6A9\uD558\uBA74 \uC6D0\uBCF8 \uC11C\uC2DD(\uD14C\uB450\uB9AC, \uD3F0\uD2B8, \uBCD1\uD569 \uB4F1)\uC744 100% \uC720\uC9C0\uD569\uB2C8\uB2E4.",
383
394
  {
384
395
  file_path: z.string().min(1).describe("\uC11C\uC2DD \uD15C\uD50C\uB9BF \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C (HWP, HWPX, PDF, XLSX, DOCX)"),
385
396
  fields: z.record(z.string(), z.string()).describe('\uCC44\uC6B8 \uD544\uB4DC \uB9F5 (\uB77C\uBCA8 \u2192 \uAC12). \uC608: {"\uC131\uBA85": "\uD64D\uAE38\uB3D9", "\uC804\uD654\uBC88\uD638": "010-1234-5678"}'),
397
+ formats: z.record(z.string(), z.string()).optional().describe("\uD544\uB4DC\uBCC4 \uAC12 \uC11C\uC2DD (\uB77C\uBCA8 \u2192 \uD3EC\uB9F7). \uC815\uC900\uAC12 \uD558\uB098\uB85C \uC11C\uC2DD\uB9C8\uB2E4 \uB2E4\uB978 \uBAA8\uC591\uC744 \uCC44\uC6B8 \uB54C: date:yy.mm.dd / phone:hyphen\xB7dot\xB7digits / rrn:hyphen\xB7masked / mask:###-## / \uC790\uC720 \uD328\uD134(yyyy\uB144 m\uC6D4 d\uC77C, ###-####-####)"),
398
+ require_unique: z.boolean().optional().describe("\uD55C \uD0A4\uAC00 \uC11C\uC2DD\uC758 2\uACF3 \uC774\uC0C1\uC5D0 \uB9E4\uCE6D\uB418\uBA74 \uCC44\uC6B0\uC9C0 \uC54A\uACE0 \uAC70\uBD80 \u2014 \uBC18\uBCF5 \uB77C\uBCA8 \uC591\uC2DD\uC5D0\uC11C \uB0A8\uC758 \uBE14\uB85D \uC624\uC5FC \uBC29\uC9C0 (\uBC30\uC5F4 \uAC12\uC740 \uC608\uC678)"),
399
+ mask_values: z.boolean().optional().describe("\uC751\uB2F5\uC5D0 \uAC12 \uB300\uC2E0 \uAE00\uC790\uC218\uB9CC \uD45C\uC2DC \u2014 \uAC1C\uC778\uC815\uBCF4 \uCC44\uC6C0 \uC2DC \uAC12\uC774 \uB300\uD654 \uB85C\uADF8\uC5D0 \uB0A8\uC9C0 \uC54A\uAC8C"),
386
400
  output_format: z.enum(["markdown", "hwpx", "hwpx-preserve"]).default("hwpx-preserve").describe("\uCD9C\uB825 \uD3EC\uB9F7: hwpx-preserve (\uC6D0\uBCF8 \uC2A4\uD0C0\uC77C \uBCF4\uC874, HWPX \uC804\uC6A9), hwpx (\uC0C8 HWPX \uC0DD\uC131), markdown"),
387
401
  output_path: z.string().optional().describe("\uCD9C\uB825 \uD30C\uC77C \uC800\uC7A5 \uACBD\uB85C (\uC120\uD0DD). \uC9C0\uC815 \uC2DC \uD30C\uC77C\uB85C \uC800\uC7A5, \uBBF8\uC9C0\uC815 \uC2DC \uD14D\uC2A4\uD2B8\uB85C \uBC18\uD658")
388
402
  },
389
- async ({ file_path, fields, output_format, output_path }) => {
403
+ async ({ file_path, fields, formats, require_unique, mask_values, output_format, output_path }) => {
390
404
  try {
391
405
  const { buffer } = readValidatedFile(file_path);
392
406
  if (output_format === "hwpx-preserve") {
@@ -402,12 +416,21 @@ server.tool(
402
416
  isError: true
403
417
  };
404
418
  }
405
- const hwpxResult = await fillHwpx(buffer, fields);
419
+ const inputs = buildFillInputs(fields, formats);
420
+ const hwpxResult = require_unique ? await fillWithUniqueGuard(inputs, (vals) => fillHwpx(buffer, vals)) : { ...await fillHwpx(buffer, inputs), rejected: [] };
421
+ let verifyLine = null;
422
+ if (mask_values && hwpxResult.filled.length > 0) {
423
+ const reparsed = await parse(Buffer.from(hwpxResult.buffer));
424
+ const okCount = reparsed.success ? hwpxResult.filled.filter((f) => reparsed.markdown.includes(f.value)).length : 0;
425
+ verifyLine = `\uAC80\uC99D(\uB9C8\uC2A4\uD0B9): ${okCount}/${hwpxResult.filled.length} FILLED \u2014 \uC7AC\uD30C\uC2F1 \uB300\uC870, \uAC12 \uBBF8\uB178\uCD9C`;
426
+ }
406
427
  const summary2 = [
407
428
  `\uCC44\uC6CC\uC9C4 \uD544\uB4DC: ${hwpxResult.filled.length}\uAC1C (\uC6D0\uBCF8 \uC2A4\uD0C0\uC77C \uBCF4\uC874)`,
408
- hwpxResult.unmatched.length > 0 ? `\uB9E4\uCE6D \uC2E4\uD328: ${hwpxResult.unmatched.join(", ")}` : null
429
+ hwpxResult.rejected.length > 0 ? `\uBAA8\uD638 \uB77C\uBCA8 \uAC70\uBD80(2\uACF3+ \uB9E4\uCE6D): ${hwpxResult.rejected.join(", ")}` : null,
430
+ hwpxResult.unmatched.length > 0 ? `\uB9E4\uCE6D \uC2E4\uD328: ${hwpxResult.unmatched.join(", ")}` : null,
431
+ verifyLine
409
432
  ].filter(Boolean).join(" | ");
410
- const filledList = hwpxResult.filled.map((f) => ` - ${f.label}: ${f.value}`).join("\n");
433
+ const filledList = hwpxResult.filled.map((f) => ` - ${f.label}: ${mask_values ? `[${[...f.value].length}\uC790]` : f.value}`).join("\n");
411
434
  if (output_path) {
412
435
  mkdirSync(dirname(resolve(output_path)), { recursive: true });
413
436
  writeFileSync(resolve(output_path), Buffer.from(hwpxResult.buffer));
@@ -437,7 +460,8 @@ ${filledList}
437
460
  };
438
461
  }
439
462
  const formInfo = extractFormFields(result.blocks);
440
- const fillResult = fillFormFields(result.blocks, fields);
463
+ const irInputs = buildFillInputs(fields, formats);
464
+ const fillResult = require_unique ? await fillWithUniqueGuard(irInputs, (vals) => fillFormFields(result.blocks, vals)) : { ...fillFormFields(result.blocks, irInputs), rejected: [] };
441
465
  if (fillResult.filled.length === 0 && formInfo.fields.length === 0) {
442
466
  return {
443
467
  content: [{ type: "text", text: `\uC11C\uC2DD \uD544\uB4DC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uBC18 \uBB38\uC11C\uC774\uAC70\uB098 \uC11C\uC2DD \uD328\uD134\uC774 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.` }],
@@ -447,6 +471,7 @@ ${filledList}
447
471
  const markdown = blocksToMarkdown(fillResult.blocks);
448
472
  const summary = [
449
473
  `\uCC44\uC6CC\uC9C4 \uD544\uB4DC: ${fillResult.filled.length}\uAC1C`,
474
+ fillResult.rejected.length > 0 ? `\uBAA8\uD638 \uB77C\uBCA8 \uAC70\uBD80(2\uACF3+ \uB9E4\uCE6D): ${fillResult.rejected.join(", ")}` : null,
450
475
  fillResult.unmatched.length > 0 ? `\uB9E4\uCE6D \uC2E4\uD328: ${fillResult.unmatched.join(", ")}` : null,
451
476
  formInfo.fields.length > 0 ? `\uC11C\uC2DD \uD544\uB4DC: ${formInfo.fields.length}\uAC1C (\uD655\uC2E0\uB3C4 ${(formInfo.confidence * 100).toFixed(0)}%)` : null
452
477
  ].filter(Boolean).join(" | ");
@@ -493,6 +518,62 @@ ${markdown}` }]
493
518
  }
494
519
  }
495
520
  );
521
+ server.tool(
522
+ "place_seal",
523
+ '\uB3C4\uC7A5/\uC11C\uBA85 \uC774\uBBF8\uC9C0\uB97C \uC575\uCEE4 \uBB38\uAD6C("(\uC778)"\xB7"\uC11C\uBA85 \uB610\uB294 \uC778" \uB4F1) \uC704\uC5D0 \uBD80\uC720(\uAE00 \uC55E) \uBC30\uCE58\uD569\uB2C8\uB2E4. \uD45C/\uD398\uC774\uC9C0\uB97C \uD0A4\uC6B0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (HWPX \uC804\uC6A9).',
524
+ {
525
+ file_path: z.string().min(1).describe("\uB300\uC0C1 HWPX \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C"),
526
+ image_path: z.string().min(1).describe("\uB3C4\uC7A5/\uC11C\uBA85 \uC774\uBBF8\uC9C0 \uC808\uB300 \uACBD\uB85C (\uD22C\uBA85 \uBC30\uACBD PNG \uAD8C\uC7A5)"),
527
+ anchor: z.string().default("(\uC778)").describe("\uC575\uCEE4 \uBB38\uAD6C \u2014 \uC774 \uBB38\uAD6C \uAE30\uC900\uC73C\uB85C \uBC30\uCE58"),
528
+ occurrence: z.number().int().min(0).default(0).describe("\uAC19\uC740 \uC575\uCEE4\uAC00 \uC5EC\uB7FF\uC77C \uB54C 0-based \uC120\uD0DD"),
529
+ size_mm: z.number().positive().optional().describe("\uB3C4\uC7A5 \uD55C \uBCC0 \uD06C\uAE30 mm (\uAE30\uBCF8: \uC904\uB192\uC774\xD71.6, 7~18 \uD074\uB7A8\uD504)"),
530
+ mode: z.enum(["overlap", "right", "auto"]).default("auto").describe("overlap=\uBB38\uAD6C \uC704 \uACB9\uCE68, right=\uBB38\uAD6C \uC624\uB978\uCABD \uC606, auto=\uACF5\uAC04 \uC788\uC73C\uBA74 right"),
531
+ dx_mm: z.number().optional().describe("x \uBBF8\uC138\uC870\uC815 mm"),
532
+ dy_mm: z.number().optional().describe("y \uBBF8\uC138\uC870\uC815 mm"),
533
+ output_path: z.string().min(1).describe("\uCD9C\uB825 HWPX \uC800\uC7A5 \uACBD\uB85C")
534
+ },
535
+ async ({ file_path, image_path, anchor, occurrence, size_mm, mode, dx_mm, dy_mm, output_path }) => {
536
+ try {
537
+ const { buffer } = readValidatedFile(file_path);
538
+ const format = detectFormat(buffer);
539
+ if (format !== "hwpx") {
540
+ return {
541
+ content: [{ type: "text", text: `place_seal \uC740 HWPX \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4 (\uAC10\uC9C0\uB41C \uD3EC\uB9F7: ${format}).` }],
542
+ isError: true
543
+ };
544
+ }
545
+ const image = new Uint8Array(readFileSync(resolve(image_path)));
546
+ const ext = extname(image_path).slice(1).toLowerCase() || "png";
547
+ const { placeSealHwpx } = await import("./seal-UVDRGHEF.js");
548
+ const result = await placeSealHwpx(buffer, [{
549
+ anchor,
550
+ occurrence,
551
+ image,
552
+ ext,
553
+ sizeMm: size_mm,
554
+ mode,
555
+ dxMm: dx_mm,
556
+ dyMm: dy_mm
557
+ }]);
558
+ mkdirSync(dirname(resolve(output_path)), { recursive: true });
559
+ writeFileSync(resolve(output_path), Buffer.from(result.buffer));
560
+ const p0 = result.placed[0];
561
+ return {
562
+ content: [{
563
+ type: "text",
564
+ text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC644\uB8CC: "${p0.anchor}" #${p0.occurrence} \u2192 ${p0.mode} (x ${p0.posXMm}mm, y ${p0.posYMm}mm, ${p0.sizeMm}mm\uAC01, ${p0.entry})
565
+ \uC800\uC7A5: ${resolve(output_path)}
566
+ \uD45C/\uD398\uC774\uC9C0 \uBD88\uD655\uC7A5(\uAE00 \uC55E \uBD80\uC720) \u2014 \uD55C\uCEF4\uC5D0\uC11C \uC704\uCE58 \uD655\uC778 \uD6C4 dx_mm/dy_mm \uB85C \uBBF8\uC138\uC870\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`
567
+ }]
568
+ };
569
+ } catch (err) {
570
+ return {
571
+ content: [{ type: "text", text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC2E4\uD328: ${sanitizeError(err)}` }],
572
+ isError: true
573
+ };
574
+ }
575
+ }
576
+ );
496
577
  server.tool(
497
578
  "patch_document",
498
579
  "\uC6D0\uBCF8 HWPX/HWP\uC758 \uC11C\uC2DD(\uAE00\uAF34\xB7\uD45C\xB7\uB3C4\uC7A5\uCE78\xB7\uC774\uBBF8\uC9C0)\uC744 1\uBC14\uC774\uD2B8\uB3C4 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uACE0, \uD3B8\uC9D1\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC758 \uBC14\uB010 \uD14D\uC2A4\uD2B8\uB9CC \uC81C\uC790\uB9AC \uCE58\uD658\uD574 \uC0C8 \uBB38\uC11C\uB85C \uCD9C\uB825\uD569\uB2C8\uB2E4. parse_document\uB85C \uC5BB\uC740 \uB9C8\uD06C\uB2E4\uC6B4\uC744 \uC218\uC815\uD574 \uB118\uAE30\uC138\uC694 \u2014 \uC591\uC2DD \uBE48\uCE78 \uCC44\uC6B0\uAE30\xB7\uBB38\uAD6C \uC218\uC815\uC5D0 \uC801\uD569\uD558\uBA70 \uD55C\uCEF4 \uD55C\uAE00\uC5D0\uC11C \uBCC0\uC870 \uACBD\uACE0 \uC5C6\uC774 \uC5F4\uB9BD\uB2C8\uB2E4. (\uBE14\uB85D \uCD94\uAC00/\uC0AD\uC81C\xB7\uD45C \uAD6C\uC870 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0, \uBBF8\uC801\uC6A9 \uD56D\uBAA9\uC740 \uACB0\uACFC\uC5D0 \uBCF4\uACE0)",
package/dist/mcp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["/** kordoc MCP 서버 — Claude/Cursor에서 문서 파싱 도구로 사용 */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport { z } from \"zod\"\nimport { readFileSync, writeFileSync, realpathSync, openSync, readSync, closeSync, statSync, mkdirSync } from \"fs\"\nimport { resolve, isAbsolute, extname, dirname } from \"path\"\nimport { parse, detectFormat, detectZipFormat, blocksToMarkdown, compare, extractFormFields, fillFormFields, markdownToHwpx, fillHwpx, patchHwpx, patchHwp } from \"./index.js\"\nimport type { GongmunOptions } from \"./index.js\"\nimport { VERSION, toArrayBuffer, sanitizeError, KordocError } from \"./utils.js\"\nimport { extractHwp5MetadataOnly } from \"./hwp5/parser.js\"\nimport { extractHwpxMetadataOnly } from \"./hwpx/parser.js\"\n// pdfjs-dist는 optional — dynamic import로 지연 로드\n// import { extractPdfMetadataOnly } from \"./pdf/parser.js\"\n\n/** 허용 파일 확장자 */\nconst ALLOWED_EXTENSIONS = new Set([\".hwp\", \".hwpx\", \".pdf\", \".xlsx\", \".docx\"])\n/** 최대 파일 크기 (500MB) */\nconst MAX_FILE_SIZE = 500 * 1024 * 1024\n\n/** 경로 정규화 및 보안 검증 */\nfunction safePath(filePath: string): string {\n if (!filePath) throw new KordocError(\"파일 경로가 비어있습니다\")\n const resolved = resolve(filePath)\n let real: string\n try {\n real = realpathSync(resolved)\n } catch (err: any) {\n if (err?.code === \"ENOENT\") throw new KordocError(`파일을 찾을 수 없습니다: ${resolved}`)\n if (err?.code === \"EACCES\" || err?.code === \"EPERM\") throw new KordocError(`파일 접근 권한이 없습니다: ${resolved}`)\n throw new KordocError(`경로 처리 오류 [${err?.code ?? \"UNKNOWN\"}]`)\n }\n if (!isAbsolute(real)) throw new KordocError(\"절대 경로만 허용됩니다\")\n const ext = extname(real).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) throw new KordocError(`지원하지 않는 확장자입니다: ${ext} (허용: ${[...ALLOWED_EXTENSIONS].join(\", \")})`)\n return real\n}\n\n/** 최대 파일 크기 — metadata 전용 (50MB, 전체 파싱보다 보수적) */\nconst MAX_METADATA_FILE_SIZE = 50 * 1024 * 1024\n\n/** 파일 읽기 + 크기 검증 공통 로직 */\nfunction readValidatedFile(filePath: string, maxSize = MAX_FILE_SIZE): { buffer: ArrayBuffer; resolved: string } {\n const resolved = safePath(filePath)\n let fileSize: number\n try {\n fileSize = statSync(resolved).size\n } catch (err: any) {\n throw new KordocError(`파일 상태 읽기 실패 [${err?.code ?? \"UNKNOWN\"}]: ${resolved}`)\n }\n if (fileSize > maxSize) {\n throw new KordocError(`파일이 너무 큽니다: ${(fileSize / 1024 / 1024).toFixed(1)}MB (최대 ${maxSize / 1024 / 1024}MB)`)\n }\n let raw: Buffer\n try {\n raw = readFileSync(resolved)\n } catch (err: any) {\n throw new KordocError(`파일 읽기 실패 [${err?.code ?? \"UNKNOWN\"}]: ${resolved}`)\n }\n return { buffer: toArrayBuffer(raw), resolved }\n}\n\n/** 파일 헤더(16바이트)만 읽어 포맷 감지 — 전체 파일 로드 불필요 */\nfunction detectFormatFromHeader(resolved: string): ReturnType<typeof detectFormat> {\n const fd = openSync(resolved, \"r\")\n try {\n const headerBuf = Buffer.alloc(16)\n readSync(fd, headerBuf, 0, 16, 0)\n return detectFormat(toArrayBuffer(headerBuf))\n } finally {\n closeSync(fd)\n }\n}\n\nconst server = new McpServer({\n name: \"kordoc\",\n version: VERSION,\n})\n\n// ─── 도구: parse_document ────────────────────────────\n\nserver.tool(\n \"parse_document\",\n \"한국 문서 파일(HWP, HWPX, PDF, XLSX, DOCX)을 마크다운으로 변환합니다. 파일 경로를 입력하면 포맷을 자동 감지하여 텍스트를 추출합니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로 (HWP, HWPX, PDF, XLSX, DOCX)\"),\n },\n async ({ file_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const meta = [\n `포맷: ${result.fileType.toUpperCase()}`,\n result.pageCount ? `페이지: ${result.pageCount}` : null,\n result.metadata?.title ? `제목: ${result.metadata.title}` : null,\n result.metadata?.author ? `작성자: ${result.metadata.author}` : null,\n result.isImageBased ? \"이미지 기반 PDF (텍스트 추출 불가)\" : null,\n ].filter(Boolean).join(\" | \")\n\n // outline/warnings 부가 정보 추가\n const parts: string[] = [`[${meta}]`]\n\n if (result.outline && result.outline.length > 0) {\n const outlineText = result.outline.map(o => `${\" \".repeat(o.level - 1)}- ${o.text}`).join(\"\\n\")\n parts.push(`\\n📑 문서 구조:\\n${outlineText}`)\n }\n\n if (result.warnings && result.warnings.length > 0) {\n const warnText = result.warnings.map(w => `- [p${w.page || \"?\"}] ${w.message}`).join(\"\\n\")\n parts.push(`\\n⚠️ 경고:\\n${warnText}`)\n }\n\n parts.push(`\\n\\n${result.markdown}`)\n\n return {\n content: [{ type: \"text\", text: parts.join(\"\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: detect_format ─────────────────────────────\n\nserver.tool(\n \"detect_format\",\n \"파일의 포맷을 매직 바이트로 감지합니다 (hwpx, hwp, pdf, unknown).\",\n {\n file_path: z.string().min(1).describe(\"감지할 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const resolved = safePath(file_path)\n const format = detectFormatFromHeader(resolved)\n return {\n content: [{ type: \"text\", text: `${file_path}: ${format}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_metadata ────────────────────────────\n\nserver.tool(\n \"parse_metadata\",\n \"문서의 메타데이터(제목, 작성자, 날짜 등)만 빠르게 추출합니다. 전체 파싱 없이 헤더/매니페스트만 읽습니다.\",\n {\n file_path: z.string().min(1).describe(\"메타데이터를 추출할 문서 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const resolved = safePath(file_path)\n const format = detectFormatFromHeader(resolved)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n // metadata 전용 크기 제한 (50MB)\n const { buffer } = readValidatedFile(file_path, MAX_METADATA_FILE_SIZE)\n\n let metadata\n // ZIP 기반 포맷(hwpx)은 내부 구조로 세분화 (XLSX/DOCX 구분)\n let effectiveFormat = format\n if (format === \"hwpx\") {\n const { detectZipFormat } = await import(\"./detect.js\")\n const zipFormat = await detectZipFormat(buffer)\n if (zipFormat === \"xlsx\" || zipFormat === \"docx\") effectiveFormat = zipFormat as any\n }\n switch (effectiveFormat) {\n case \"hwp\":\n metadata = extractHwp5MetadataOnly(Buffer.from(buffer))\n break\n case \"hwpx\":\n metadata = await extractHwpxMetadataOnly(buffer)\n break\n case \"pdf\":\n try {\n const { extractPdfMetadataOnly } = await import(\"./pdf/parser.js\")\n metadata = await extractPdfMetadataOnly(buffer)\n } catch {\n metadata = undefined // pdfjs-dist 미설치 시 metadata 생략\n }\n break\n case \"xlsx\":\n case \"docx\": {\n // XLSX/DOCX는 전용 metadata 추출기가 없으므로 전체 파싱 후 metadata 반환\n const result = await parse(buffer)\n metadata = result.success ? result.metadata : undefined\n break\n }\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify({ format, ...metadata }, null, 2) }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_pages ──────────────────────────────\n\nserver.tool(\n \"parse_pages\",\n \"문서의 특정 페이지/섹션 범위만 파싱합니다. PDF는 정확한 페이지, HWP/HWPX는 섹션 단위 근사치입니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로\"),\n pages: z.string().min(1).describe(\"페이지 범위 (예: '1-3', '1,3,5-7')\"),\n },\n async ({ file_path, pages }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer, { pages })\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const meta = [\n `포맷: ${result.fileType.toUpperCase()}`,\n `범위: ${pages}`,\n result.pageCount ? `페이지: ${result.pageCount}` : null,\n ].filter(Boolean).join(\" | \")\n\n return {\n content: [{ type: \"text\", text: `[${meta}]\\n\\n${result.markdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_table ──────────────────────────────\n\nserver.tool(\n \"parse_table\",\n \"문서에서 N번째 테이블만 추출합니다 (0-based index). 테이블이 없거나 인덱스 범위를 초과하면 오류를 반환합니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로\"),\n table_index: z.number().int().min(0).describe(\"추출할 테이블 인덱스 (0부터 시작)\"),\n },\n async ({ file_path, table_index }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const tableBlocks = result.blocks.filter(b => b.type === \"table\" && b.table)\n if (tableBlocks.length === 0) {\n return {\n content: [{ type: \"text\", text: `문서에 테이블이 없습니다.` }],\n isError: true,\n }\n }\n\n if (table_index >= tableBlocks.length) {\n return {\n content: [{ type: \"text\", text: `테이블 인덱스 초과: ${table_index} (총 ${tableBlocks.length}개 테이블)` }],\n isError: true,\n }\n }\n\n const tableBlock = tableBlocks[table_index]\n const tableMarkdown = blocksToMarkdown([tableBlock])\n\n return {\n content: [{ type: \"text\", text: `[테이블 #${table_index} / 총 ${tableBlocks.length}개]\\n\\n${tableMarkdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: compare_documents ─────────────────────────\n\nserver.tool(\n \"compare_documents\",\n \"두 한국 문서 파일을 비교하여 추가/삭제/변경된 블록을 표시합니다. 신구대조표 생성에 활용됩니다. 크로스 포맷(HWP↔HWPX) 비교 가능.\",\n {\n file_path_a: z.string().min(1).describe(\"비교 원본 문서의 절대 경로\"),\n file_path_b: z.string().min(1).describe(\"비교 대상 문서의 절대 경로\"),\n },\n async ({ file_path_a, file_path_b }) => {\n try {\n const { buffer: bufA } = readValidatedFile(file_path_a)\n const { buffer: bufB } = readValidatedFile(file_path_b)\n\n const result = await compare(bufA, bufB)\n const { stats, diffs } = result\n\n const lines: string[] = [\n `## 문서 비교 결과`,\n `추가: ${stats.added} | 삭제: ${stats.removed} | 변경: ${stats.modified} | 동일: ${stats.unchanged}`,\n \"\",\n ]\n\n for (const d of diffs) {\n const prefix = d.type === \"added\" ? \"+\" : d.type === \"removed\" ? \"-\" : d.type === \"modified\" ? \"~\" : \" \"\n const text = d.after?.text || d.before?.text || (d.after?.table ? \"[테이블]\" : d.before?.table ? \"[테이블]\" : \"\")\n const sim = d.similarity !== undefined ? ` (${(d.similarity * 100).toFixed(0)}%)` : \"\"\n lines.push(`${prefix} ${text.substring(0, 200)}${sim}`)\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_form ───────────────────────────────\n\nserver.tool(\n \"parse_form\",\n \"한국 서식 문서에서 레이블-값 쌍을 구조화된 JSON으로 추출합니다. 양식/서식 문서에 최적화.\",\n {\n file_path: z.string().min(1).describe(\"서식 문서 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패: ${result.error}` }],\n isError: true,\n }\n }\n\n const form = extractFormFields(result.blocks)\n return {\n content: [{ type: \"text\", text: JSON.stringify(form, null, 2) }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: fill_form ───────────────────────────────\n\nserver.tool(\n \"fill_form\",\n \"한국 서식 문서의 빈칸을 채워서 새 문서로 출력합니다. hwpx-preserve를 사용하면 원본 서식(테두리, 폰트, 병합 등)을 100% 유지합니다.\",\n {\n file_path: z.string().min(1).describe(\"서식 템플릿 문서의 절대 경로 (HWP, HWPX, PDF, XLSX, DOCX)\"),\n fields: z.record(z.string(), z.string()).describe(\"채울 필드 맵 (라벨 → 값). 예: {\\\"성명\\\": \\\"홍길동\\\", \\\"전화번호\\\": \\\"010-1234-5678\\\"}\"),\n output_format: z.enum([\"markdown\", \"hwpx\", \"hwpx-preserve\"]).default(\"hwpx-preserve\").describe(\"출력 포맷: hwpx-preserve (원본 스타일 보존, HWPX 전용), hwpx (새 HWPX 생성), markdown\"),\n output_path: z.string().optional().describe(\"출력 파일 저장 경로 (선택). 지정 시 파일로 저장, 미지정 시 텍스트로 반환\"),\n },\n async ({ file_path, fields, output_format, output_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n\n // ─── hwpx-preserve: 원본 ZIP 직접 수정 (스타일 보존) ───\n if (output_format === \"hwpx-preserve\") {\n const format = detectFormat(buffer)\n let isHwpx = format === \"hwpx\"\n if (isHwpx) {\n const zipFormat = await detectZipFormat(buffer)\n isHwpx = zipFormat === \"hwpx\"\n }\n if (!isHwpx) {\n return {\n content: [{ type: \"text\", text: `hwpx-preserve는 HWPX 파일만 지원합니다 (감지된 포맷: ${format}). hwpx 또는 markdown을 사용하세요.` }],\n isError: true,\n }\n }\n\n const hwpxResult = await fillHwpx(buffer, fields)\n const summary = [\n `채워진 필드: ${hwpxResult.filled.length}개 (원본 스타일 보존)`,\n hwpxResult.unmatched.length > 0 ? `매칭 실패: ${hwpxResult.unmatched.join(\", \")}` : null,\n ].filter(Boolean).join(\" | \")\n\n const filledList = hwpxResult.filled.map(f => ` - ${f.label}: ${f.value}`).join(\"\\n\")\n\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), Buffer.from(hwpxResult.buffer))\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n채워진 필드:\\n${filledList}\\n\\nHWPX 파일 저장 (원본 서식 유지): ${resolve(output_path)}` }],\n }\n }\n\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n채워진 필드:\\n${filledList}\\n\\n⚠️ output_path를 지정하면 원본 서식이 유지된 HWPX 파일로 저장됩니다.` }],\n }\n }\n\n // ─── 일반 경로: parse → fill → output ───\n const result = await parse(buffer)\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패: ${result.error}` }],\n isError: true,\n }\n }\n\n const formInfo = extractFormFields(result.blocks)\n const fillResult = fillFormFields(result.blocks, fields)\n\n if (fillResult.filled.length === 0 && formInfo.fields.length === 0) {\n return {\n content: [{ type: \"text\", text: `서식 필드를 찾을 수 없습니다. 일반 문서이거나 서식 패턴이 감지되지 않았습니다.` }],\n isError: true,\n }\n }\n\n const markdown = blocksToMarkdown(fillResult.blocks)\n const summary = [\n `채워진 필드: ${fillResult.filled.length}개`,\n fillResult.unmatched.length > 0 ? `매칭 실패: ${fillResult.unmatched.join(\", \")}` : null,\n formInfo.fields.length > 0 ? `서식 필드: ${formInfo.fields.length}개 (확신도 ${(formInfo.confidence * 100).toFixed(0)}%)` : null,\n ].filter(Boolean).join(\" | \")\n\n if (output_format === \"hwpx\") {\n const hwpxBuffer = await markdownToHwpx(markdown)\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), Buffer.from(hwpxBuffer))\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\nHWPX 파일 저장: ${resolve(output_path)}` }],\n }\n }\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n⚠️ output_path를 지정하면 HWPX 파일로 저장됩니다. 미리보기:\\n\\n${markdown}` }],\n }\n }\n\n // markdown\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), markdown, \"utf-8\")\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n마크다운 파일 저장: ${resolve(output_path)}\\n\\n${markdown}` }],\n }\n }\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n${markdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: patch_document ────────────────────────────\n\nserver.tool(\n \"patch_document\",\n \"원본 HWPX/HWP의 서식(글꼴·표·도장칸·이미지)을 1바이트도 건드리지 않고, 편집된 마크다운의 바뀐 텍스트만 제자리 치환해 새 문서로 출력합니다. parse_document로 얻은 마크다운을 수정해 넘기세요 — 양식 빈칸 채우기·문구 수정에 적합하며 한컴 한글에서 변조 경고 없이 열립니다. (블록 추가/삭제·표 구조 변경은 미지원, 미적용 항목은 결과에 보고)\",\n {\n file_path: z.string().min(1).describe(\"원본 문서의 절대 경로 (HWPX 또는 HWP 5.x)\"),\n edited_markdown: z.string().min(1).describe(\"parse_document 출력 마크다운을 편집한 전체 마크다운. 바뀐 문단/셀 텍스트만 반영하고 블록 수·순서는 원본과 같게 유지하세요\"),\n output_path: z.string().min(1).describe(\"출력 파일 저장 절대 경로 (원본과 같은 확장자: .hwpx 또는 .hwp)\"),\n },\n async ({ file_path, edited_markdown, output_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n let isHwpx = format === \"hwpx\"\n if (isHwpx) {\n const zipFormat = await detectZipFormat(buffer)\n isHwpx = zipFormat === \"hwpx\"\n }\n if (!isHwpx && format !== \"hwp\") {\n return {\n content: [{ type: \"text\", text: `patch_document는 HWPX 또는 HWP 5.x만 지원합니다 (감지된 포맷: ${format}).` }],\n isError: true,\n }\n }\n\n const original = new Uint8Array(buffer)\n const result = isHwpx\n ? await patchHwpx(original, edited_markdown)\n : await patchHwp(original, edited_markdown)\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: \"text\", text: `패치 실패: ${result.error ?? \"알 수 없는 오류\"}` }],\n isError: true,\n }\n }\n\n const out = resolve(output_path)\n mkdirSync(dirname(out), { recursive: true })\n writeFileSync(out, Buffer.from(result.data))\n\n const v = result.verification?.stats\n const lossless = v ? (v.modified === 0 && v.added === 0 && v.removed === 0) : undefined\n const lines = [\n `✓ ${result.applied}개 변경 적용 (${isHwpx ? \"HWPX\" : \"HWP\"}, 원본 서식 보존) → ${out}`,\n lossless === true ? \"검증: 편집 내용과 재파싱 결과 완전 일치\" :\n lossless === false ? `검증 잔차: 수정 ${v!.modified} · 추가 ${v!.added} · 삭제 ${v!.removed} (반영 안 된 편집 있음)` : null,\n result.skipped.length > 0\n ? `미적용 ${result.skipped.length}건:\\n` + result.skipped.map(s => ` - ${s.reason}`).join(\"\\n\")\n : null,\n ].filter(Boolean)\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: generate_document ─────────────────────────\n\nserver.tool(\n \"generate_document\",\n \"마크다운을 HWPX 한글 문서로 생성합니다. GFM 표(| 헤더 | … |)·헤딩·리스트·볼드를 한글 문서 요소로 변환하며, 공문서 프리셋 지정 시 행정 표준 서식(항목부호 8단계·공식 여백·명조 15pt)으로 렌더링합니다. 활용: 평문 문장을 표로 구조화해 새 한글파일로 만들거나, parse_document로 읽은 내용을 편집해 다시 HWPX로 출력. (원본 서식을 보존하며 일부 텍스트/표만 제자리 수정하려면 patch_document 사용)\",\n {\n markdown: z.string().min(1).describe(\"HWPX로 변환할 마크다운 전문. 표는 GFM 문법 사용 (예: '| 이름 | 부서 |\\\\n| --- | --- |\\\\n| 홍길동 | 기획팀 |')\"),\n output_path: z.string().min(1).describe(\"출력 HWPX 파일의 절대 경로 (.hwpx 권장)\"),\n preset: z.enum([\"기안문\", \"보고서\", \"계획서\", \"통지\", \"회의록\", \"official\", \"report\", \"plan\", \"notice\", \"minutes\"]).optional()\n .describe(\"공문서 프리셋 — 지정 시 한국 행정 공문서 표준 서식 적용. 미지정 시 범용 마크다운 변환\"),\n font: z.enum([\"myeongjo\", \"gothic\"]).optional().describe(\"본문 글꼴(공문서 모드): myeongjo=함초롬바탕(명조), gothic=맑은 고딕\"),\n body_pt: z.number().int().min(6).max(40).optional().describe(\"본문 글자 크기(pt, 공문서 모드). 기본 15\"),\n },\n async ({ markdown, output_path, preset, font, body_pt }) => {\n try {\n let gongmun: GongmunOptions | undefined\n if (preset) {\n gongmun = { preset }\n if (font) gongmun.bodyFont = font\n if (body_pt) gongmun.bodyPt = body_pt\n }\n const buf = await markdownToHwpx(markdown, gongmun ? { gongmun } : undefined)\n const out = resolve(output_path)\n mkdirSync(dirname(out), { recursive: true })\n writeFileSync(out, Buffer.from(buf))\n\n const mode = gongmun ? `공문서:${gongmun.preset}` : \"범용\"\n const tableCount = (markdown.match(/^\\s*\\|.*\\|\\s*$/gm) || []).length > 0\n ? `, 표 포함` : \"\"\n return {\n content: [{ type: \"text\", text: `✓ HWPX 생성 (${mode}${tableCount}) → ${out}\\n크기: ${(buf.byteLength / 1024).toFixed(1)}KB` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 서버 시작 ───────────────────────────────────────\n\nasync function main() {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => { console.error(err); process.exit(1) })\n"],"mappings":"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","names":["detectZipFormat","summary"]}
1
+ {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["/** kordoc MCP 서버 — Claude/Cursor에서 문서 파싱 도구로 사용 */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport { z } from \"zod\"\nimport { readFileSync, writeFileSync, realpathSync, openSync, readSync, closeSync, statSync, mkdirSync } from \"fs\"\nimport { resolve, isAbsolute, extname, dirname } from \"path\"\nimport { parse, detectFormat, detectZipFormat, blocksToMarkdown, compare, extractFormFields, fillFormFields, markdownToHwpx, fillHwpx, patchHwpx, patchHwp } from \"./index.js\"\nimport { fillWithUniqueGuard, type FillInput } from \"./form/match.js\"\nimport type { GongmunOptions } from \"./index.js\"\nimport { VERSION, toArrayBuffer, sanitizeError, KordocError } from \"./utils.js\"\nimport { extractHwp5MetadataOnly } from \"./hwp5/parser.js\"\nimport { extractHwpxMetadataOnly } from \"./hwpx/parser.js\"\n// pdfjs-dist는 optional — dynamic import로 지연 로드\n// import { extractPdfMetadataOnly } from \"./pdf/parser.js\"\n\n/** 허용 파일 확장자 */\nconst ALLOWED_EXTENSIONS = new Set([\".hwp\", \".hwpx\", \".pdf\", \".xlsx\", \".docx\"])\n/** 최대 파일 크기 (500MB) */\nconst MAX_FILE_SIZE = 500 * 1024 * 1024\n\n/** 경로 정규화 및 보안 검증 */\nfunction safePath(filePath: string): string {\n if (!filePath) throw new KordocError(\"파일 경로가 비어있습니다\")\n const resolved = resolve(filePath)\n let real: string\n try {\n real = realpathSync(resolved)\n } catch (err: any) {\n if (err?.code === \"ENOENT\") throw new KordocError(`파일을 찾을 수 없습니다: ${resolved}`)\n if (err?.code === \"EACCES\" || err?.code === \"EPERM\") throw new KordocError(`파일 접근 권한이 없습니다: ${resolved}`)\n throw new KordocError(`경로 처리 오류 [${err?.code ?? \"UNKNOWN\"}]`)\n }\n if (!isAbsolute(real)) throw new KordocError(\"절대 경로만 허용됩니다\")\n const ext = extname(real).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) throw new KordocError(`지원하지 않는 확장자입니다: ${ext} (허용: ${[...ALLOWED_EXTENSIONS].join(\", \")})`)\n return real\n}\n\n/** 최대 파일 크기 — metadata 전용 (50MB, 전체 파싱보다 보수적) */\nconst MAX_METADATA_FILE_SIZE = 50 * 1024 * 1024\n\n/** 파일 읽기 + 크기 검증 공통 로직 */\nfunction readValidatedFile(filePath: string, maxSize = MAX_FILE_SIZE): { buffer: ArrayBuffer; resolved: string } {\n const resolved = safePath(filePath)\n let fileSize: number\n try {\n fileSize = statSync(resolved).size\n } catch (err: any) {\n throw new KordocError(`파일 상태 읽기 실패 [${err?.code ?? \"UNKNOWN\"}]: ${resolved}`)\n }\n if (fileSize > maxSize) {\n throw new KordocError(`파일이 너무 큽니다: ${(fileSize / 1024 / 1024).toFixed(1)}MB (최대 ${maxSize / 1024 / 1024}MB)`)\n }\n let raw: Buffer\n try {\n raw = readFileSync(resolved)\n } catch (err: any) {\n throw new KordocError(`파일 읽기 실패 [${err?.code ?? \"UNKNOWN\"}]: ${resolved}`)\n }\n return { buffer: toArrayBuffer(raw), resolved }\n}\n\n/** 파일 헤더(16바이트)만 읽어 포맷 감지 — 전체 파일 로드 불필요 */\nfunction detectFormatFromHeader(resolved: string): ReturnType<typeof detectFormat> {\n const fd = openSync(resolved, \"r\")\n try {\n const headerBuf = Buffer.alloc(16)\n readSync(fd, headerBuf, 0, 16, 0)\n return detectFormat(toArrayBuffer(headerBuf))\n } finally {\n closeSync(fd)\n }\n}\n\nconst server = new McpServer({\n name: \"kordoc\",\n version: VERSION,\n})\n\n// ─── 도구: parse_document ────────────────────────────\n\nserver.tool(\n \"parse_document\",\n \"한국 문서 파일(HWP, HWPX, PDF, XLSX, DOCX)을 마크다운으로 변환합니다. 파일 경로를 입력하면 포맷을 자동 감지하여 텍스트를 추출합니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로 (HWP, HWPX, PDF, XLSX, DOCX)\"),\n },\n async ({ file_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const meta = [\n `포맷: ${result.fileType.toUpperCase()}`,\n result.pageCount ? `페이지: ${result.pageCount}` : null,\n result.metadata?.title ? `제목: ${result.metadata.title}` : null,\n result.metadata?.author ? `작성자: ${result.metadata.author}` : null,\n result.isImageBased ? \"이미지 기반 PDF (텍스트 추출 불가)\" : null,\n ].filter(Boolean).join(\" | \")\n\n // outline/warnings 부가 정보 추가\n const parts: string[] = [`[${meta}]`]\n\n if (result.outline && result.outline.length > 0) {\n const outlineText = result.outline.map(o => `${\" \".repeat(o.level - 1)}- ${o.text}`).join(\"\\n\")\n parts.push(`\\n📑 문서 구조:\\n${outlineText}`)\n }\n\n if (result.warnings && result.warnings.length > 0) {\n const warnText = result.warnings.map(w => `- [p${w.page || \"?\"}] ${w.message}`).join(\"\\n\")\n parts.push(`\\n⚠️ 경고:\\n${warnText}`)\n }\n\n parts.push(`\\n\\n${result.markdown}`)\n\n return {\n content: [{ type: \"text\", text: parts.join(\"\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: detect_format ─────────────────────────────\n\nserver.tool(\n \"detect_format\",\n \"파일의 포맷을 매직 바이트로 감지합니다 (hwpx, hwp, pdf, unknown).\",\n {\n file_path: z.string().min(1).describe(\"감지할 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const resolved = safePath(file_path)\n const format = detectFormatFromHeader(resolved)\n return {\n content: [{ type: \"text\", text: `${file_path}: ${format}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_metadata ────────────────────────────\n\nserver.tool(\n \"parse_metadata\",\n \"문서의 메타데이터(제목, 작성자, 날짜 등)만 빠르게 추출합니다. 전체 파싱 없이 헤더/매니페스트만 읽습니다.\",\n {\n file_path: z.string().min(1).describe(\"메타데이터를 추출할 문서 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const resolved = safePath(file_path)\n const format = detectFormatFromHeader(resolved)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n // metadata 전용 크기 제한 (50MB)\n const { buffer } = readValidatedFile(file_path, MAX_METADATA_FILE_SIZE)\n\n let metadata\n // ZIP 기반 포맷(hwpx)은 내부 구조로 세분화 (XLSX/DOCX 구분)\n let effectiveFormat = format\n if (format === \"hwpx\") {\n const { detectZipFormat } = await import(\"./detect.js\")\n const zipFormat = await detectZipFormat(buffer)\n if (zipFormat === \"xlsx\" || zipFormat === \"docx\") effectiveFormat = zipFormat as any\n }\n switch (effectiveFormat) {\n case \"hwp\":\n metadata = extractHwp5MetadataOnly(Buffer.from(buffer))\n break\n case \"hwpx\":\n metadata = await extractHwpxMetadataOnly(buffer)\n break\n case \"pdf\":\n try {\n const { extractPdfMetadataOnly } = await import(\"./pdf/parser.js\")\n metadata = await extractPdfMetadataOnly(buffer)\n } catch {\n metadata = undefined // pdfjs-dist 미설치 시 metadata 생략\n }\n break\n case \"xlsx\":\n case \"docx\": {\n // XLSX/DOCX는 전용 metadata 추출기가 없으므로 전체 파싱 후 metadata 반환\n const result = await parse(buffer)\n metadata = result.success ? result.metadata : undefined\n break\n }\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify({ format, ...metadata }, null, 2) }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_pages ──────────────────────────────\n\nserver.tool(\n \"parse_pages\",\n \"문서의 특정 페이지/섹션 범위만 파싱합니다. PDF는 정확한 페이지, HWP/HWPX는 섹션 단위 근사치입니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로\"),\n pages: z.string().min(1).describe(\"페이지 범위 (예: '1-3', '1,3,5-7')\"),\n },\n async ({ file_path, pages }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer, { pages })\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const meta = [\n `포맷: ${result.fileType.toUpperCase()}`,\n `범위: ${pages}`,\n result.pageCount ? `페이지: ${result.pageCount}` : null,\n ].filter(Boolean).join(\" | \")\n\n return {\n content: [{ type: \"text\", text: `[${meta}]\\n\\n${result.markdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_table ──────────────────────────────\n\nserver.tool(\n \"parse_table\",\n \"문서에서 N번째 테이블만 추출합니다 (0-based index). 테이블이 없거나 인덱스 범위를 초과하면 오류를 반환합니다.\",\n {\n file_path: z.string().min(1).describe(\"파싱할 문서 파일의 절대 경로\"),\n table_index: z.number().int().min(0).describe(\"추출할 테이블 인덱스 (0부터 시작)\"),\n },\n async ({ file_path, table_index }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n\n if (format === \"unknown\") {\n return {\n content: [{ type: \"text\", text: `지원하지 않는 파일 형식입니다: ${file_path}` }],\n isError: true,\n }\n }\n\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패 (${result.fileType}): ${result.error}` }],\n isError: true,\n }\n }\n\n const tableBlocks = result.blocks.filter(b => b.type === \"table\" && b.table)\n if (tableBlocks.length === 0) {\n return {\n content: [{ type: \"text\", text: `문서에 테이블이 없습니다.` }],\n isError: true,\n }\n }\n\n if (table_index >= tableBlocks.length) {\n return {\n content: [{ type: \"text\", text: `테이블 인덱스 초과: ${table_index} (총 ${tableBlocks.length}개 테이블)` }],\n isError: true,\n }\n }\n\n const tableBlock = tableBlocks[table_index]\n const tableMarkdown = blocksToMarkdown([tableBlock])\n\n return {\n content: [{ type: \"text\", text: `[테이블 #${table_index} / 총 ${tableBlocks.length}개]\\n\\n${tableMarkdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: compare_documents ─────────────────────────\n\nserver.tool(\n \"compare_documents\",\n \"두 한국 문서 파일을 비교하여 추가/삭제/변경된 블록을 표시합니다. 신구대조표 생성에 활용됩니다. 크로스 포맷(HWP↔HWPX) 비교 가능.\",\n {\n file_path_a: z.string().min(1).describe(\"비교 원본 문서의 절대 경로\"),\n file_path_b: z.string().min(1).describe(\"비교 대상 문서의 절대 경로\"),\n },\n async ({ file_path_a, file_path_b }) => {\n try {\n const { buffer: bufA } = readValidatedFile(file_path_a)\n const { buffer: bufB } = readValidatedFile(file_path_b)\n\n const result = await compare(bufA, bufB)\n const { stats, diffs } = result\n\n const lines: string[] = [\n `## 문서 비교 결과`,\n `추가: ${stats.added} | 삭제: ${stats.removed} | 변경: ${stats.modified} | 동일: ${stats.unchanged}`,\n \"\",\n ]\n\n for (const d of diffs) {\n const prefix = d.type === \"added\" ? \"+\" : d.type === \"removed\" ? \"-\" : d.type === \"modified\" ? \"~\" : \" \"\n const text = d.after?.text || d.before?.text || (d.after?.table ? \"[테이블]\" : d.before?.table ? \"[테이블]\" : \"\")\n const sim = d.similarity !== undefined ? ` (${(d.similarity * 100).toFixed(0)}%)` : \"\"\n lines.push(`${prefix} ${text.substring(0, 200)}${sim}`)\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: parse_form ───────────────────────────────\n\nserver.tool(\n \"parse_form\",\n \"한국 서식 문서에서 레이블-값 쌍을 구조화된 JSON으로 추출합니다. 양식/서식 문서에 최적화.\",\n {\n file_path: z.string().min(1).describe(\"서식 문서 파일의 절대 경로\"),\n },\n async ({ file_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const result = await parse(buffer)\n\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패: ${result.error}` }],\n isError: true,\n }\n }\n\n const form = extractFormFields(result.blocks)\n return {\n content: [{ type: \"text\", text: JSON.stringify(form, null, 2) }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n/** fields + formats 를 FillInput 맵으로 결합 (formats의 라벨은 fields와 동일 표기 기준) */\nfunction buildFillInputs(fields: Record<string, string>, formats?: Record<string, string>): Record<string, FillInput> {\n const out: Record<string, FillInput> = {}\n for (const [k, v] of Object.entries(fields)) {\n const format = formats?.[k]\n out[k] = format ? { value: v, format } : v\n }\n return out\n}\n\n// ─── 도구: fill_form ───────────────────────────────\n\nserver.tool(\n \"fill_form\",\n \"한국 서식 문서의 빈칸을 채워서 새 문서로 출력합니다. hwpx-preserve를 사용하면 원본 서식(테두리, 폰트, 병합 등)을 100% 유지합니다.\",\n {\n file_path: z.string().min(1).describe(\"서식 템플릿 문서의 절대 경로 (HWP, HWPX, PDF, XLSX, DOCX)\"),\n fields: z.record(z.string(), z.string()).describe(\"채울 필드 맵 (라벨 → 값). 예: {\\\"성명\\\": \\\"홍길동\\\", \\\"전화번호\\\": \\\"010-1234-5678\\\"}\"),\n formats: z.record(z.string(), z.string()).optional().describe(\"필드별 값 서식 (라벨 → 포맷). 정준값 하나로 서식마다 다른 모양을 채울 때: date:yy.mm.dd / phone:hyphen·dot·digits / rrn:hyphen·masked / mask:###-## / 자유 패턴(yyyy년 m월 d일, ###-####-####)\"),\n require_unique: z.boolean().optional().describe(\"한 키가 서식의 2곳 이상에 매칭되면 채우지 않고 거부 — 반복 라벨 양식에서 남의 블록 오염 방지 (배열 값은 예외)\"),\n mask_values: z.boolean().optional().describe(\"응답에 값 대신 글자수만 표시 — 개인정보 채움 시 값이 대화 로그에 남지 않게\"),\n output_format: z.enum([\"markdown\", \"hwpx\", \"hwpx-preserve\"]).default(\"hwpx-preserve\").describe(\"출력 포맷: hwpx-preserve (원본 스타일 보존, HWPX 전용), hwpx (새 HWPX 생성), markdown\"),\n output_path: z.string().optional().describe(\"출력 파일 저장 경로 (선택). 지정 시 파일로 저장, 미지정 시 텍스트로 반환\"),\n },\n async ({ file_path, fields, formats, require_unique, mask_values, output_format, output_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n\n // ─── hwpx-preserve: 원본 ZIP 직접 수정 (스타일 보존) ───\n if (output_format === \"hwpx-preserve\") {\n const format = detectFormat(buffer)\n let isHwpx = format === \"hwpx\"\n if (isHwpx) {\n const zipFormat = await detectZipFormat(buffer)\n isHwpx = zipFormat === \"hwpx\"\n }\n if (!isHwpx) {\n return {\n content: [{ type: \"text\", text: `hwpx-preserve는 HWPX 파일만 지원합니다 (감지된 포맷: ${format}). hwpx 또는 markdown을 사용하세요.` }],\n isError: true,\n }\n }\n\n const inputs = buildFillInputs(fields, formats)\n const hwpxResult = require_unique\n ? await fillWithUniqueGuard(inputs, vals => fillHwpx(buffer, vals))\n : { ...(await fillHwpx(buffer, inputs)), rejected: [] as string[] }\n // 마스킹 verify — 채운 결과를 재파싱해 값이 실제 문서에 있는지만 확인 (값 미노출)\n let verifyLine: string | null = null\n if (mask_values && hwpxResult.filled.length > 0) {\n const reparsed = await parse(Buffer.from(hwpxResult.buffer))\n const okCount = reparsed.success\n ? hwpxResult.filled.filter(f => reparsed.markdown.includes(f.value)).length\n : 0\n verifyLine = `검증(마스킹): ${okCount}/${hwpxResult.filled.length} FILLED — 재파싱 대조, 값 미노출`\n }\n const summary = [\n `채워진 필드: ${hwpxResult.filled.length}개 (원본 스타일 보존)`,\n hwpxResult.rejected.length > 0 ? `모호 라벨 거부(2곳+ 매칭): ${hwpxResult.rejected.join(\", \")}` : null,\n hwpxResult.unmatched.length > 0 ? `매칭 실패: ${hwpxResult.unmatched.join(\", \")}` : null,\n verifyLine,\n ].filter(Boolean).join(\" | \")\n\n const filledList = hwpxResult.filled\n .map(f => ` - ${f.label}: ${mask_values ? `[${[...f.value].length}자]` : f.value}`).join(\"\\n\")\n\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), Buffer.from(hwpxResult.buffer))\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n채워진 필드:\\n${filledList}\\n\\nHWPX 파일 저장 (원본 서식 유지): ${resolve(output_path)}` }],\n }\n }\n\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n채워진 필드:\\n${filledList}\\n\\n⚠️ output_path를 지정하면 원본 서식이 유지된 HWPX 파일로 저장됩니다.` }],\n }\n }\n\n // ─── 일반 경로: parse → fill → output ───\n const result = await parse(buffer)\n if (!result.success) {\n return {\n content: [{ type: \"text\", text: `파싱 실패: ${result.error}` }],\n isError: true,\n }\n }\n\n const formInfo = extractFormFields(result.blocks)\n const irInputs = buildFillInputs(fields, formats)\n const fillResult = require_unique\n ? await fillWithUniqueGuard(irInputs, vals => fillFormFields(result.blocks, vals))\n : { ...fillFormFields(result.blocks, irInputs), rejected: [] as string[] }\n\n if (fillResult.filled.length === 0 && formInfo.fields.length === 0) {\n return {\n content: [{ type: \"text\", text: `서식 필드를 찾을 수 없습니다. 일반 문서이거나 서식 패턴이 감지되지 않았습니다.` }],\n isError: true,\n }\n }\n\n const markdown = blocksToMarkdown(fillResult.blocks)\n const summary = [\n `채워진 필드: ${fillResult.filled.length}개`,\n fillResult.rejected.length > 0 ? `모호 라벨 거부(2곳+ 매칭): ${fillResult.rejected.join(\", \")}` : null,\n fillResult.unmatched.length > 0 ? `매칭 실패: ${fillResult.unmatched.join(\", \")}` : null,\n formInfo.fields.length > 0 ? `서식 필드: ${formInfo.fields.length}개 (확신도 ${(formInfo.confidence * 100).toFixed(0)}%)` : null,\n ].filter(Boolean).join(\" | \")\n\n if (output_format === \"hwpx\") {\n const hwpxBuffer = await markdownToHwpx(markdown)\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), Buffer.from(hwpxBuffer))\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\nHWPX 파일 저장: ${resolve(output_path)}` }],\n }\n }\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n⚠️ output_path를 지정하면 HWPX 파일로 저장됩니다. 미리보기:\\n\\n${markdown}` }],\n }\n }\n\n // markdown\n if (output_path) {\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), markdown, \"utf-8\")\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n마크다운 파일 저장: ${resolve(output_path)}\\n\\n${markdown}` }],\n }\n }\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n${markdown}` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: place_seal ─────────────────────────────\n\nserver.tool(\n \"place_seal\",\n \"도장/서명 이미지를 앵커 문구(\\\"(인)\\\"·\\\"서명 또는 인\\\" 등) 위에 부유(글 앞) 배치합니다. 표/페이지를 키우지 않습니다 (HWPX 전용).\",\n {\n file_path: z.string().min(1).describe(\"대상 HWPX 문서의 절대 경로\"),\n image_path: z.string().min(1).describe(\"도장/서명 이미지 절대 경로 (투명 배경 PNG 권장)\"),\n anchor: z.string().default(\"(인)\").describe(\"앵커 문구 — 이 문구 기준으로 배치\"),\n occurrence: z.number().int().min(0).default(0).describe(\"같은 앵커가 여럿일 때 0-based 선택\"),\n size_mm: z.number().positive().optional().describe(\"도장 한 변 크기 mm (기본: 줄높이×1.6, 7~18 클램프)\"),\n mode: z.enum([\"overlap\", \"right\", \"auto\"]).default(\"auto\").describe(\"overlap=문구 위 겹침, right=문구 오른쪽 옆, auto=공간 있으면 right\"),\n dx_mm: z.number().optional().describe(\"x 미세조정 mm\"),\n dy_mm: z.number().optional().describe(\"y 미세조정 mm\"),\n output_path: z.string().min(1).describe(\"출력 HWPX 저장 경로\"),\n },\n async ({ file_path, image_path, anchor, occurrence, size_mm, mode, dx_mm, dy_mm, output_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n if (format !== \"hwpx\") {\n return {\n content: [{ type: \"text\", text: `place_seal 은 HWPX 파일만 지원합니다 (감지된 포맷: ${format}).` }],\n isError: true,\n }\n }\n const image = new Uint8Array(readFileSync(resolve(image_path)))\n const ext = (extname(image_path).slice(1).toLowerCase() || \"png\") as \"png\" | \"jpg\" | \"jpeg\" | \"bmp\" | \"gif\"\n const { placeSealHwpx } = await import(\"./form/seal.js\")\n const result = await placeSealHwpx(buffer, [{\n anchor, occurrence, image, ext,\n sizeMm: size_mm, mode, dxMm: dx_mm, dyMm: dy_mm,\n }])\n mkdirSync(dirname(resolve(output_path)), { recursive: true })\n writeFileSync(resolve(output_path), Buffer.from(result.buffer))\n const p0 = result.placed[0]\n return {\n content: [{\n type: \"text\",\n text: `도장 배치 완료: \"${p0.anchor}\" #${p0.occurrence} → ${p0.mode} (x ${p0.posXMm}mm, y ${p0.posYMm}mm, ${p0.sizeMm}mm각, ${p0.entry})\\n저장: ${resolve(output_path)}\\n표/페이지 불확장(글 앞 부유) — 한컴에서 위치 확인 후 dx_mm/dy_mm 로 미세조정 가능합니다.`,\n }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `도장 배치 실패: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n },\n)\n\n// ─── 도구: patch_document ────────────────────────────\n\nserver.tool(\n \"patch_document\",\n \"원본 HWPX/HWP의 서식(글꼴·표·도장칸·이미지)을 1바이트도 건드리지 않고, 편집된 마크다운의 바뀐 텍스트만 제자리 치환해 새 문서로 출력합니다. parse_document로 얻은 마크다운을 수정해 넘기세요 — 양식 빈칸 채우기·문구 수정에 적합하며 한컴 한글에서 변조 경고 없이 열립니다. (블록 추가/삭제·표 구조 변경은 미지원, 미적용 항목은 결과에 보고)\",\n {\n file_path: z.string().min(1).describe(\"원본 문서의 절대 경로 (HWPX 또는 HWP 5.x)\"),\n edited_markdown: z.string().min(1).describe(\"parse_document 출력 마크다운을 편집한 전체 마크다운. 바뀐 문단/셀 텍스트만 반영하고 블록 수·순서는 원본과 같게 유지하세요\"),\n output_path: z.string().min(1).describe(\"출력 파일 저장 절대 경로 (원본과 같은 확장자: .hwpx 또는 .hwp)\"),\n },\n async ({ file_path, edited_markdown, output_path }) => {\n try {\n const { buffer } = readValidatedFile(file_path)\n const format = detectFormat(buffer)\n let isHwpx = format === \"hwpx\"\n if (isHwpx) {\n const zipFormat = await detectZipFormat(buffer)\n isHwpx = zipFormat === \"hwpx\"\n }\n if (!isHwpx && format !== \"hwp\") {\n return {\n content: [{ type: \"text\", text: `patch_document는 HWPX 또는 HWP 5.x만 지원합니다 (감지된 포맷: ${format}).` }],\n isError: true,\n }\n }\n\n const original = new Uint8Array(buffer)\n const result = isHwpx\n ? await patchHwpx(original, edited_markdown)\n : await patchHwp(original, edited_markdown)\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: \"text\", text: `패치 실패: ${result.error ?? \"알 수 없는 오류\"}` }],\n isError: true,\n }\n }\n\n const out = resolve(output_path)\n mkdirSync(dirname(out), { recursive: true })\n writeFileSync(out, Buffer.from(result.data))\n\n const v = result.verification?.stats\n const lossless = v ? (v.modified === 0 && v.added === 0 && v.removed === 0) : undefined\n const lines = [\n `✓ ${result.applied}개 변경 적용 (${isHwpx ? \"HWPX\" : \"HWP\"}, 원본 서식 보존) → ${out}`,\n lossless === true ? \"검증: 편집 내용과 재파싱 결과 완전 일치\" :\n lossless === false ? `검증 잔차: 수정 ${v!.modified} · 추가 ${v!.added} · 삭제 ${v!.removed} (반영 안 된 편집 있음)` : null,\n result.skipped.length > 0\n ? `미적용 ${result.skipped.length}건:\\n` + result.skipped.map(s => ` - ${s.reason}`).join(\"\\n\")\n : null,\n ].filter(Boolean)\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 도구: generate_document ─────────────────────────\n\nserver.tool(\n \"generate_document\",\n \"마크다운을 HWPX 한글 문서로 생성합니다. GFM 표(| 헤더 | … |)·헤딩·리스트·볼드를 한글 문서 요소로 변환하며, 공문서 프리셋 지정 시 행정 표준 서식(항목부호 8단계·공식 여백·명조 15pt)으로 렌더링합니다. 활용: 평문 문장을 표로 구조화해 새 한글파일로 만들거나, parse_document로 읽은 내용을 편집해 다시 HWPX로 출력. (원본 서식을 보존하며 일부 텍스트/표만 제자리 수정하려면 patch_document 사용)\",\n {\n markdown: z.string().min(1).describe(\"HWPX로 변환할 마크다운 전문. 표는 GFM 문법 사용 (예: '| 이름 | 부서 |\\\\n| --- | --- |\\\\n| 홍길동 | 기획팀 |')\"),\n output_path: z.string().min(1).describe(\"출력 HWPX 파일의 절대 경로 (.hwpx 권장)\"),\n preset: z.enum([\"기안문\", \"보고서\", \"계획서\", \"통지\", \"회의록\", \"official\", \"report\", \"plan\", \"notice\", \"minutes\"]).optional()\n .describe(\"공문서 프리셋 — 지정 시 한국 행정 공문서 표준 서식 적용. 미지정 시 범용 마크다운 변환\"),\n font: z.enum([\"myeongjo\", \"gothic\"]).optional().describe(\"본문 글꼴(공문서 모드): myeongjo=함초롬바탕(명조), gothic=맑은 고딕\"),\n body_pt: z.number().int().min(6).max(40).optional().describe(\"본문 글자 크기(pt, 공문서 모드). 기본 15\"),\n },\n async ({ markdown, output_path, preset, font, body_pt }) => {\n try {\n let gongmun: GongmunOptions | undefined\n if (preset) {\n gongmun = { preset }\n if (font) gongmun.bodyFont = font\n if (body_pt) gongmun.bodyPt = body_pt\n }\n const buf = await markdownToHwpx(markdown, gongmun ? { gongmun } : undefined)\n const out = resolve(output_path)\n mkdirSync(dirname(out), { recursive: true })\n writeFileSync(out, Buffer.from(buf))\n\n const mode = gongmun ? `공문서:${gongmun.preset}` : \"범용\"\n const tableCount = (markdown.match(/^\\s*\\|.*\\|\\s*$/gm) || []).length > 0\n ? `, 표 포함` : \"\"\n return {\n content: [{ type: \"text\", text: `✓ HWPX 생성 (${mode}${tableCount}) → ${out}\\n크기: ${(buf.byteLength / 1024).toFixed(1)}KB` }],\n }\n } catch (err) {\n return {\n content: [{ type: \"text\", text: `오류: ${sanitizeError(err)}` }],\n isError: true,\n }\n }\n }\n)\n\n// ─── 서버 시작 ───────────────────────────────────────\n\nasync function main() {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => { console.error(err); process.exit(1) })\n"],"mappings":"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","names":["detectZipFormat","summary"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/mong-e/workspace/kordoc/dist/page-range-P7SDW6LR.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACF,0DAAC","file":"/Users/mong-e/workspace/kordoc/dist/page-range-P7SDW6LR.cjs"}
1
+ {"version":3,"sources":["/Users/chris_gomdori/workspace/kordoc/dist/page-range-P7SDW6LR.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACF,0DAAC","file":"/Users/chris_gomdori/workspace/kordoc/dist/page-range-P7SDW6LR.cjs"}
@@ -6,7 +6,7 @@ import {
6
6
  blocksToMarkdown,
7
7
  safeMax,
8
8
  safeMin
9
- } from "./chunk-O4WFPOFN.js";
9
+ } from "./chunk-Q7EGGW3H.js";
10
10
  import {
11
11
  parsePageRange
12
12
  } from "./chunk-GE43BE46.js";
@@ -216,15 +216,45 @@ function dropShadingStacks(lines, dir) {
216
216
  else groups.set(key, [l]);
217
217
  }
218
218
  const dropped = /* @__PURE__ */ new Set();
219
+ const coord = (l) => dir === "h" ? l.y1 : l.x1;
219
220
  for (const group of groups.values()) {
220
221
  if (group.length < STACK_MIN_LINES) continue;
221
- group.sort((a, b) => dir === "h" ? a.y1 - b.y1 : a.x1 - b.x1);
222
+ group.sort((a, b) => coord(a) - coord(b));
222
223
  let runStart = 0;
223
224
  for (let i = 1; i <= group.length; i++) {
224
- const gap = i < group.length ? dir === "h" ? group[i].y1 - group[i - 1].y1 : group[i].x1 - group[i - 1].x1 : Infinity;
225
+ const gap = i < group.length ? coord(group[i]) - coord(group[i - 1]) : Infinity;
225
226
  if (gap < STACK_GAP) continue;
226
227
  if (i - runStart >= STACK_MIN_LINES) {
227
- for (let j = runStart; j < i; j++) dropped.add(group[j]);
228
+ let s = runStart;
229
+ let e = i - 1;
230
+ const wKey = (l) => Math.round(l.lineWidth * 100);
231
+ const wCount = /* @__PURE__ */ new Map();
232
+ for (let j = s; j <= e; j++) wCount.set(wKey(group[j]), (wCount.get(wKey(group[j])) ?? 0) + 1);
233
+ let domW = 0;
234
+ let domN = 0;
235
+ for (const [w, n] of wCount) if (n > domN) {
236
+ domW = w;
237
+ domN = n;
238
+ }
239
+ const pitches = [];
240
+ for (let j = s + 1; j <= e; j++) pitches.push(coord(group[j]) - coord(group[j - 1]));
241
+ pitches.sort((a, b) => a - b);
242
+ const medPitch = pitches[Math.floor(pitches.length / 2)] ?? 0;
243
+ const edgeAlien = (j, inwardGap) => wKey(group[j]) !== domW || medPitch > 0 && inwardGap > medPitch * 1.8;
244
+ while (e - s + 1 >= STACK_MIN_LINES) {
245
+ if (edgeAlien(s, coord(group[s + 1]) - coord(group[s]))) {
246
+ s++;
247
+ continue;
248
+ }
249
+ if (edgeAlien(e, coord(group[e]) - coord(group[e - 1]))) {
250
+ e--;
251
+ continue;
252
+ }
253
+ break;
254
+ }
255
+ if (e - s + 1 >= STACK_MIN_LINES) {
256
+ for (let j = s; j <= e; j++) dropped.add(group[j]);
257
+ }
228
258
  }
229
259
  runStart = i;
230
260
  }
@@ -237,6 +267,7 @@ var EDGE_MIN_SPAN = 12;
237
267
  var EDGE_INSET = 15;
238
268
  var EDGE_NEAR = 10;
239
269
  var EDGE_CONNECT_TOL = 5;
270
+ var EDGE_YGAP_SPLIT_K = 2.5;
240
271
  var CHAIN_Y_TOL = 1.5;
241
272
  var CHAIN_GAP = 3;
242
273
  function chainCollinearRules(horizontals) {
@@ -281,8 +312,25 @@ function closeOpenTableEdges(horizontals, verticals) {
281
312
  }
282
313
  if (!placed) groups.push([hl]);
283
314
  }
284
- const synthesized = [];
315
+ const splitGroups = [];
285
316
  for (const g2 of groups) {
317
+ const sorted = [...g2].sort((a, b) => a.y1 - b.y1);
318
+ const gaps = [];
319
+ for (let i = 1; i < sorted.length; i++) gaps.push(sorted[i].y1 - sorted[i - 1].y1);
320
+ const median = gaps.length ? [...gaps].sort((a, b) => a - b)[gaps.length >> 1] : 0;
321
+ const threshold = median * EDGE_YGAP_SPLIT_K;
322
+ let cur = sorted.length ? [sorted[0]] : [];
323
+ for (let i = 1; i < sorted.length; i++) {
324
+ if (median > 0 && sorted[i].y1 - sorted[i - 1].y1 > threshold && cur.length >= EDGE_MIN_RULES && sorted.length - i >= EDGE_MIN_RULES) {
325
+ splitGroups.push(cur);
326
+ cur = [];
327
+ }
328
+ cur.push(sorted[i]);
329
+ }
330
+ if (cur.length) splitGroups.push(cur);
331
+ }
332
+ const synthesized = [];
333
+ for (const g2 of splitGroups) {
286
334
  if (g2.length < EDGE_MIN_RULES) continue;
287
335
  let yMin = Infinity, yMax = -Infinity, x1 = 0, x2 = 0;
288
336
  for (const hl of g2) {
@@ -3523,4 +3571,4 @@ export {
3523
3571
  parsePdfDocument,
3524
3572
  removeHeaderFooterBlocks
3525
3573
  };
3526
- //# sourceMappingURL=parser-Z3IL2ESY.js.map
3574
+ //# sourceMappingURL=parser-5G247XYD.js.map