kordoc 3.16.0 → 3.16.2

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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/{-H7N62NZU.js → -C4FVXHIP.js} +7 -7
  3. package/dist/{chunk-IC56KHBL.js → chunk-4WI7QXEG.js} +2 -2
  4. package/dist/{chunk-BIP4JAVG.cjs → chunk-72LLOLFX.cjs} +13 -6
  5. package/dist/chunk-72LLOLFX.cjs.map +1 -0
  6. package/dist/{chunk-5RHRQ7HK.js → chunk-E4Y73XRH.js} +8 -4
  7. package/dist/chunk-E4Y73XRH.js.map +1 -0
  8. package/dist/{chunk-Q7EGGW3H.js → chunk-EJ2ZIYTO.js} +13 -6
  9. package/dist/chunk-EJ2ZIYTO.js.map +1 -0
  10. package/dist/{chunk-WD5NUUU6.js → chunk-EUEKDZ7O.js} +315 -262
  11. package/dist/chunk-EUEKDZ7O.js.map +1 -0
  12. package/dist/{chunk-JFNWJFKP.js → chunk-HECUYZZK.js} +2 -2
  13. package/dist/{chunk-LYL4IL3E.js → chunk-IS3IK7UT.js} +13 -6
  14. package/dist/chunk-IS3IK7UT.js.map +1 -0
  15. package/dist/{chunk-FGL7BH4P.js → chunk-WR5LHRXO.js} +49 -20
  16. package/dist/chunk-WR5LHRXO.js.map +1 -0
  17. package/dist/cli.js +56 -17
  18. package/dist/cli.js.map +1 -1
  19. package/dist/index.cjs +586 -502
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +11 -3
  22. package/dist/index.d.ts +11 -3
  23. package/dist/index.js +358 -274
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp.js +25 -16
  26. package/dist/mcp.js.map +1 -1
  27. package/dist/{parser-HBHCD7TE.js → parser-CMPNM6ZL.js} +25 -12
  28. package/dist/parser-CMPNM6ZL.js.map +1 -0
  29. package/dist/{parser-5G247XYD.js → parser-GWL72XYS.js} +24 -11
  30. package/dist/parser-GWL72XYS.js.map +1 -0
  31. package/dist/{parser-6V7F3GET.cjs → parser-YVRTKGKO.cjs} +36 -23
  32. package/dist/parser-YVRTKGKO.cjs.map +1 -0
  33. package/dist/render-A2LFMJCP.js +10 -0
  34. package/dist/seal-KA4VAV2Z.js +10 -0
  35. package/dist/{watch-HIYNOSFD.js → watch-WLI6K3IR.js} +7 -7
  36. package/package.json +2 -5
  37. package/dist/chunk-5RHRQ7HK.js.map +0 -1
  38. package/dist/chunk-BIP4JAVG.cjs.map +0 -1
  39. package/dist/chunk-FGL7BH4P.js.map +0 -1
  40. package/dist/chunk-LYL4IL3E.js.map +0 -1
  41. package/dist/chunk-Q7EGGW3H.js.map +0 -1
  42. package/dist/chunk-WD5NUUU6.js.map +0 -1
  43. package/dist/parser-5G247XYD.js.map +0 -1
  44. package/dist/parser-6V7F3GET.cjs.map +0 -1
  45. package/dist/parser-HBHCD7TE.js.map +0 -1
  46. package/dist/render-VA4NHROW.js +0 -10
  47. package/dist/seal-UVDRGHEF.js +0 -10
  48. /package/dist/{-H7N62NZU.js.map → -C4FVXHIP.js.map} +0 -0
  49. /package/dist/{chunk-IC56KHBL.js.map → chunk-4WI7QXEG.js.map} +0 -0
  50. /package/dist/{chunk-JFNWJFKP.js.map → chunk-HECUYZZK.js.map} +0 -0
  51. /package/dist/{render-VA4NHROW.js.map → render-A2LFMJCP.js.map} +0 -0
  52. /package/dist/{seal-UVDRGHEF.js.map → seal-KA4VAV2Z.js.map} +0 -0
  53. /package/dist/{watch-HIYNOSFD.js.map → watch-WLI6K3IR.js.map} +0 -0
package/dist/mcp.js CHANGED
@@ -11,24 +11,24 @@ import {
11
11
  parse,
12
12
  patchHwp,
13
13
  patchHwpx
14
- } from "./chunk-WD5NUUU6.js";
14
+ } from "./chunk-EUEKDZ7O.js";
15
15
  import {
16
16
  blocksToMarkdown
17
- } from "./chunk-LYL4IL3E.js";
17
+ } from "./chunk-IS3IK7UT.js";
18
18
  import {
19
19
  detectFormat,
20
20
  detectZipFormat
21
21
  } from "./chunk-MEPHGCPQ.js";
22
22
  import "./chunk-MOL7MDBG.js";
23
- import "./chunk-FGL7BH4P.js";
24
- import "./chunk-5RHRQ7HK.js";
25
- import "./chunk-JFNWJFKP.js";
23
+ import "./chunk-WR5LHRXO.js";
24
+ import "./chunk-E4Y73XRH.js";
25
+ import "./chunk-HECUYZZK.js";
26
26
  import {
27
27
  KordocError,
28
28
  VERSION,
29
29
  sanitizeError,
30
30
  toArrayBuffer
31
- } from "./chunk-IC56KHBL.js";
31
+ } from "./chunk-4WI7QXEG.js";
32
32
 
33
33
  // src/mcp.ts
34
34
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -199,7 +199,7 @@ server.tool(
199
199
  break;
200
200
  case "pdf":
201
201
  try {
202
- const { extractPdfMetadataOnly } = await import("./parser-HBHCD7TE.js");
202
+ const { extractPdfMetadataOnly } = await import("./parser-CMPNM6ZL.js");
203
203
  metadata = await extractPdfMetadataOnly(buffer);
204
204
  } catch {
205
205
  metadata = void 0;
@@ -417,11 +417,13 @@ server.tool(
417
417
  };
418
418
  }
419
419
  const inputs = buildFillInputs(fields, formats);
420
- const hwpxResult = require_unique ? await fillWithUniqueGuard(inputs, (vals) => fillHwpx(buffer, vals)) : { ...await fillHwpx(buffer, inputs), rejected: [] };
420
+ const hwpxResult = require_unique ? await fillWithUniqueGuard(inputs, (vals, blocked) => fillHwpx(buffer, vals, blocked)) : { ...await fillHwpx(buffer, inputs), rejected: [] };
421
421
  let verifyLine = null;
422
422
  if (mask_values && hwpxResult.filled.length > 0) {
423
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;
424
+ const norm = (s) => s.replace(/\\([\\`*_{}[\]()#+.!|~>-])/g, "$1").replace(/\s+/g, " ");
425
+ const normMd = reparsed.success ? norm(reparsed.markdown) : "";
426
+ const okCount = reparsed.success ? hwpxResult.filled.filter((f) => f.value !== "" && normMd.includes(norm(f.value))).length : 0;
425
427
  verifyLine = `\uAC80\uC99D(\uB9C8\uC2A4\uD0B9): ${okCount}/${hwpxResult.filled.length} FILLED \u2014 \uC7AC\uD30C\uC2F1 \uB300\uC870, \uAC12 \uBBF8\uB178\uCD9C`;
426
428
  }
427
429
  const summary2 = [
@@ -461,7 +463,7 @@ ${filledList}
461
463
  }
462
464
  const formInfo = extractFormFields(result.blocks);
463
465
  const irInputs = buildFillInputs(fields, formats);
464
- const fillResult = require_unique ? await fillWithUniqueGuard(irInputs, (vals) => fillFormFields(result.blocks, vals)) : { ...fillFormFields(result.blocks, irInputs), rejected: [] };
466
+ const fillResult = require_unique ? await fillWithUniqueGuard(irInputs, (vals, blocked) => fillFormFields(result.blocks, vals, blocked)) : { ...fillFormFields(result.blocks, irInputs), rejected: [] };
465
467
  if (fillResult.filled.length === 0 && formInfo.fields.length === 0) {
466
468
  return {
467
469
  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.` }],
@@ -469,6 +471,7 @@ ${filledList}
469
471
  };
470
472
  }
471
473
  const markdown = blocksToMarkdown(fillResult.blocks);
474
+ const previewMd = mask_values ? "\u26A0\uFE0F mask_values \uD65C\uC131 \u2014 \uAC1C\uC778\uC815\uBCF4 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uBCF8\uBB38\uC744 \uC751\uB2F5\uC5D0 \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. output_path \uB85C \uD30C\uC77C \uC800\uC7A5 \uD6C4 \uD655\uC778\uD558\uC138\uC694." : markdown;
472
475
  const summary = [
473
476
  `\uCC44\uC6CC\uC9C4 \uD544\uB4DC: ${fillResult.filled.length}\uAC1C`,
474
477
  fillResult.rejected.length > 0 ? `\uBAA8\uD638 \uB77C\uBCA8 \uAC70\uBD80(2\uACF3+ \uB9E4\uCE6D): ${fillResult.rejected.join(", ")}` : null,
@@ -491,7 +494,7 @@ HWPX \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}` }]
491
494
 
492
495
  \u26A0\uFE0F output_path\uB97C \uC9C0\uC815\uD558\uBA74 HWPX \uD30C\uC77C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4. \uBBF8\uB9AC\uBCF4\uAE30:
493
496
 
494
- ${markdown}` }]
497
+ ${previewMd}` }]
495
498
  };
496
499
  }
497
500
  if (output_path) {
@@ -502,13 +505,13 @@ ${markdown}` }]
502
505
 
503
506
  \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}
504
507
 
505
- ${markdown}` }]
508
+ ${previewMd}` }]
506
509
  };
507
510
  }
508
511
  return {
509
512
  content: [{ type: "text", text: `[${summary}]
510
513
 
511
- ${markdown}` }]
514
+ ${previewMd}` }]
512
515
  };
513
516
  } catch (err) {
514
517
  return {
@@ -542,9 +545,13 @@ server.tool(
542
545
  isError: true
543
546
  };
544
547
  }
545
- const image = new Uint8Array(readFileSync(resolve(image_path)));
548
+ const imgResolved = resolve(image_path);
549
+ if (statSync(imgResolved).size > 500 * 1024 * 1024) {
550
+ return { content: [{ type: "text", text: `\uB3C4\uC7A5 \uC774\uBBF8\uC9C0\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${(statSync(imgResolved).size / 1024 / 1024).toFixed(0)}MB) \u2014 500MB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.` }], isError: true };
551
+ }
552
+ const image = new Uint8Array(readFileSync(imgResolved));
546
553
  const ext = extname(image_path).slice(1).toLowerCase() || "png";
547
- const { placeSealHwpx } = await import("./seal-UVDRGHEF.js");
554
+ const { placeSealHwpx } = await import("./seal-KA4VAV2Z.js");
548
555
  const result = await placeSealHwpx(buffer, [{
549
556
  anchor,
550
557
  occurrence,
@@ -558,11 +565,13 @@ server.tool(
558
565
  mkdirSync(dirname(resolve(output_path)), { recursive: true });
559
566
  writeFileSync(resolve(output_path), Buffer.from(result.buffer));
560
567
  const p0 = result.placed[0];
568
+ const warnLines = (p0.warnings ?? []).map((w) => `
569
+ \u26A0\uFE0F ${w}`).join("");
561
570
  return {
562
571
  content: [{
563
572
  type: "text",
564
573
  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)}
574
+ \uC800\uC7A5: ${resolve(output_path)}${warnLines}
566
575
  \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
576
  }]
568
577
  };
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 { 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
+ {"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, blocked) => fillHwpx(buffer, vals, blocked))\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 // 마크다운 이스케이프(\\*,\\|,\\~ 등)·개행/연속공백 정규화 후 비교 — rrn:masked\n // ('900315-1******')의 * 이스케이프로 생기던 결정적 false negative 방지.\n // 빈 값은 includes('')===true 로 항상 통과하던 것을 FILLED 에서 제외한다.\n const norm = (s: string): string => s.replace(/\\\\([\\\\`*_{}[\\]()#+.!|~>-])/g, \"$1\").replace(/\\s+/g, \" \")\n const normMd = reparsed.success ? norm(reparsed.markdown) : \"\"\n const okCount = reparsed.success\n ? hwpxResult.filled.filter(f => f.value !== \"\" && normMd.includes(norm(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, blocked) => fillFormFields(result.blocks, vals, blocked))\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 // mask_values 시 채운 값(주민번호·연락처 등)이 응답(대화 로그)에 노출되지 않게\n // 본문 미리보기를 안내 문구로 대체 (sfill-8). 값은 output_path 파일에만 기록된다.\n const previewMd = mask_values\n ? \"⚠️ mask_values 활성 — 개인정보 노출 방지를 위해 본문을 응답에 포함하지 않습니다. output_path 로 파일 저장 후 확인하세요.\"\n : markdown\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${previewMd}` }],\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${previewMd}` }],\n }\n }\n return {\n content: [{ type: \"text\", text: `[${summary}]\\n\\n${previewMd}` }],\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 imgResolved = resolve(image_path)\n if (statSync(imgResolved).size > 500 * 1024 * 1024) {\n return { content: [{ type: \"text\", text: `도장 이미지가 너무 큽니다 (${(statSync(imgResolved).size / 1024 / 1024).toFixed(0)}MB) — 500MB 이하여야 합니다.` }], isError: true }\n }\n const image = new Uint8Array(readFileSync(imgResolved))\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 const warnLines = (p0.warnings ?? []).map(w => `\\n⚠️ ${w}`).join(\"\")\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)}${warnLines}\\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"]}
@@ -4,7 +4,7 @@ import {
4
4
  HEADING_RATIO_H2,
5
5
  HEADING_RATIO_H3,
6
6
  blocksToMarkdown
7
- } from "./chunk-LYL4IL3E.js";
7
+ } from "./chunk-IS3IK7UT.js";
8
8
  import {
9
9
  parsePageRange
10
10
  } from "./chunk-MOL7MDBG.js";
@@ -12,7 +12,7 @@ import {
12
12
  KordocError,
13
13
  safeMax,
14
14
  safeMin
15
- } from "./chunk-IC56KHBL.js";
15
+ } from "./chunk-4WI7QXEG.js";
16
16
 
17
17
  // src/pdf/line-extract.ts
18
18
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -49,14 +49,14 @@ function extractLines(fnArray, argsArray) {
49
49
  pushSeg(rx, ry + rh, rx, ry);
50
50
  }
51
51
  }
52
- function flushPath(isStroke) {
52
+ function flushPath(isStroke, fromFill = false) {
53
53
  if (!isStroke) {
54
54
  currentPath = [];
55
55
  return;
56
56
  }
57
57
  const effWidth = lineWidth * ctmScale();
58
58
  for (const seg of currentPath) {
59
- classifyAndAdd(seg, effWidth, horizontals, verticals);
59
+ classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill);
60
60
  }
61
61
  currentPath = [];
62
62
  }
@@ -154,7 +154,9 @@ function extractLines(fnArray, argsArray) {
154
154
  }
155
155
  if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {
156
156
  flushPath(true);
157
- } else if (afterOp === OPS.fill || afterOp === OPS.eoFill || afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke || afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {
157
+ } else if (afterOp === OPS.fill || afterOp === OPS.eoFill) {
158
+ flushPath(true, true);
159
+ } else if (afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke || afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {
158
160
  flushPath(true);
159
161
  } else if (afterOp === OPS.endPath) {
160
162
  flushPath(false);
@@ -168,6 +170,8 @@ function extractLines(fnArray, argsArray) {
168
170
  break;
169
171
  case OPS.fill:
170
172
  case OPS.eoFill:
173
+ flushPath(true, true);
174
+ break;
171
175
  case OPS.fillStroke:
172
176
  case OPS.eoFillStroke:
173
177
  case OPS.closeFillStroke:
@@ -181,7 +185,7 @@ function extractLines(fnArray, argsArray) {
181
185
  }
182
186
  return { horizontals, verticals };
183
187
  }
184
- function classifyAndAdd(seg, lineWidth, horizontals, verticals) {
188
+ function classifyAndAdd(seg, lineWidth, horizontals, verticals, fromFill = false) {
185
189
  const dx = Math.abs(seg.x2 - seg.x1);
186
190
  const dy = Math.abs(seg.y2 - seg.y1);
187
191
  const length = Math.sqrt(dx * dx + dy * dy);
@@ -190,12 +194,12 @@ function classifyAndAdd(seg, lineWidth, horizontals, verticals) {
190
194
  const y = (seg.y1 + seg.y2) / 2;
191
195
  const x1 = Math.min(seg.x1, seg.x2);
192
196
  const x2 = Math.max(seg.x1, seg.x2);
193
- horizontals.push({ x1, y1: y, x2, y2: y, lineWidth });
197
+ horizontals.push({ x1, y1: y, x2, y2: y, lineWidth, fromFill });
194
198
  } else if (dx <= ORIENTATION_TOL) {
195
199
  const x = (seg.x1 + seg.x2) / 2;
196
200
  const y1 = Math.min(seg.y1, seg.y2);
197
201
  const y2 = Math.max(seg.y1, seg.y2);
198
- verticals.push({ x1: x, y1, x2: x, y2, lineWidth });
202
+ verticals.push({ x1: x, y1, x2: x, y2, lineWidth, fromFill });
199
203
  }
200
204
  }
201
205
  function preprocessLines(horizontals, verticals) {
@@ -243,7 +247,10 @@ function dropShadingStacks(lines, dir) {
243
247
  for (let j = s + 1; j <= e; j++) pitches.push(coord(group[j]) - coord(group[j - 1]));
244
248
  pitches.sort((a, b) => a - b);
245
249
  const medPitch = pitches[Math.floor(pitches.length / 2)] ?? 0;
246
- const edgeAlien = (j, inwardGap) => wKey(group[j]) !== domW || medPitch > 0 && inwardGap > medPitch * 1.8;
250
+ let fillN = 0;
251
+ for (let j = s; j <= e; j++) if (group[j].fromFill) fillN++;
252
+ const stackIsFill = fillN * 2 > e - s + 1;
253
+ const edgeAlien = (j, inwardGap) => stackIsFill && !group[j].fromFill || wKey(group[j]) !== domW || medPitch > 0 && inwardGap > medPitch * 1.8;
247
254
  while (e - s + 1 >= STACK_MIN_LINES) {
248
255
  if (edgeAlien(s, coord(group[s + 1]) - coord(group[s]))) {
249
256
  s++;
@@ -271,6 +278,7 @@ var EDGE_INSET = 15;
271
278
  var EDGE_NEAR = 10;
272
279
  var EDGE_CONNECT_TOL = 5;
273
280
  var EDGE_YGAP_SPLIT_K = 2.5;
281
+ var EDGE_YGAP_ABS_MIN = 30;
274
282
  var CHAIN_Y_TOL = 1.5;
275
283
  var CHAIN_GAP = 3;
276
284
  function chainCollinearRules(horizontals) {
@@ -324,7 +332,12 @@ function closeOpenTableEdges(horizontals, verticals) {
324
332
  const threshold = median * EDGE_YGAP_SPLIT_K;
325
333
  let cur = sorted.length ? [sorted[0]] : [];
326
334
  for (let i = 1; i < sorted.length; i++) {
327
- if (median > 0 && sorted[i].y1 - sorted[i - 1].y1 > threshold && cur.length >= EDGE_MIN_RULES && sorted.length - i >= EDGE_MIN_RULES) {
335
+ const yLo = sorted[i - 1].y1, yHi = sorted[i].y1;
336
+ const gap = yHi - yLo;
337
+ const gx1 = Math.min(sorted[i - 1].x1, sorted[i].x1);
338
+ const gx2 = Math.max(sorted[i - 1].x2, sorted[i].x2);
339
+ const bridged = verticals.some((vl) => vl.y1 <= yLo + EDGE_NEAR && vl.y2 >= yHi - EDGE_NEAR && vl.x1 >= gx1 - EDGE_CONNECT_TOL && vl.x1 <= gx2 + EDGE_CONNECT_TOL);
340
+ if (median > 0 && gap > threshold && gap > EDGE_YGAP_ABS_MIN && !bridged && cur.length >= EDGE_MIN_RULES && sorted.length - i >= EDGE_MIN_RULES) {
328
341
  splitGroups.push(cur);
329
342
  cur = [];
330
343
  }
@@ -2380,7 +2393,7 @@ function shouldDemoteTable(table) {
2380
2393
  const t = c.text.trim();
2381
2394
  return t.length > 0 && t.length <= 12 && !/[□■◆○●▶ㅇ<>]/.test(t);
2382
2395
  }) && table.cells.slice(1).some((row) => row.some((c) => c.text.trim() !== ""))) return false;
2383
- if (table.rows <= 3 && table.cols <= 3) {
2396
+ if (table.rows <= 3 && table.cols <= 3 && allText.length <= 200) {
2384
2397
  const totalCells2 = table.rows * table.cols;
2385
2398
  const emptyCells2 = totalCells2 - allCells.length;
2386
2399
  if (emptyCells2 >= totalCells2 * 0.3) return true;
@@ -3574,4 +3587,4 @@ export {
3574
3587
  parsePdfDocument,
3575
3588
  removeHeaderFooterBlocks
3576
3589
  };
3577
- //# sourceMappingURL=parser-HBHCD7TE.js.map
3590
+ //# sourceMappingURL=parser-CMPNM6ZL.js.map