kordoc 4.0.8 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +50 -16
  2. package/dist/{-LE4RLXVA.js → -SXQIZHYA.js} +24 -10
  3. package/dist/{parser-TCTCSBYZ.js → chunk-26AJJCFF.js} +1427 -2027
  4. package/dist/chunk-26AJJCFF.js.map +1 -0
  5. package/dist/{parser-KMMUS73Q.js → chunk-4JPPLIIV.js} +1425 -2025
  6. package/dist/chunk-4JPPLIIV.js.map +1 -0
  7. package/dist/chunk-4VNLZ2GZ.js +13 -0
  8. package/dist/chunk-4VNLZ2GZ.js.map +1 -0
  9. package/dist/{chunk-2DZQF6YJ.js → chunk-6SHO3BNV.js} +24 -15
  10. package/dist/chunk-6SHO3BNV.js.map +1 -0
  11. package/dist/chunk-7FCNOKOR.js +144 -0
  12. package/dist/chunk-7FCNOKOR.js.map +1 -0
  13. package/dist/{chunk-6XAAYAWL.cjs → chunk-CMN3SYZ3.js} +62 -304
  14. package/dist/chunk-CMN3SYZ3.js.map +1 -0
  15. package/dist/chunk-DZIXKL7E.js +145 -0
  16. package/dist/chunk-DZIXKL7E.js.map +1 -0
  17. package/dist/chunk-ELOV2GIJ.js +82 -0
  18. package/dist/chunk-ELOV2GIJ.js.map +1 -0
  19. package/dist/chunk-F2HYH62H.cjs +130 -0
  20. package/dist/chunk-F2HYH62H.cjs.map +1 -0
  21. package/dist/{chunk-HMUEU7D7.js → chunk-FSXLSHCW.js} +3 -3
  22. package/dist/{chunk-HMUEU7D7.js.map → chunk-FSXLSHCW.js.map} +1 -1
  23. package/dist/chunk-GEPINOL6.js +83 -0
  24. package/dist/chunk-GEPINOL6.js.map +1 -0
  25. package/dist/chunk-H7OWEYH2.cjs +243 -0
  26. package/dist/chunk-H7OWEYH2.cjs.map +1 -0
  27. package/dist/{chunk-7S3M4N4E.js → chunk-IGGRLWT5.js} +683 -226
  28. package/dist/chunk-IGGRLWT5.js.map +1 -0
  29. package/dist/chunk-J2RZ444H.js +243 -0
  30. package/dist/chunk-J2RZ444H.js.map +1 -0
  31. package/dist/chunk-JP5YH3Z4.js +130 -0
  32. package/dist/chunk-JP5YH3Z4.js.map +1 -0
  33. package/dist/{parser-ITMU2WN5.cjs → chunk-LHROM2BO.cjs} +1409 -2012
  34. package/dist/chunk-LHROM2BO.cjs.map +1 -0
  35. package/dist/chunk-NNDVFNMJ.js +246 -0
  36. package/dist/chunk-NNDVFNMJ.js.map +1 -0
  37. package/dist/{chunk-RROH5WHO.js → chunk-PLIY23LY.js} +2 -2
  38. package/dist/{chunk-BNU5QGIZ.js → chunk-V6U6YS5B.js} +12 -4
  39. package/dist/chunk-V6U6YS5B.js.map +1 -0
  40. package/dist/{chunk-UEIYETCQ.js → chunk-WF2E5P2G.js} +40 -35
  41. package/dist/chunk-WF2E5P2G.js.map +1 -0
  42. package/dist/{chunk-D35VBACN.js → chunk-WQLHPOMJ.js} +7 -4
  43. package/dist/chunk-WQLHPOMJ.js.map +1 -0
  44. package/dist/{chunk-OQPILS7B.js → chunk-XTCT6KQ5.cjs} +79 -183
  45. package/dist/chunk-XTCT6KQ5.cjs.map +1 -0
  46. package/dist/{chunk-MEPHGCPQ.js → chunk-YOO6ET6M.js} +7 -3
  47. package/dist/chunk-YOO6ET6M.js.map +1 -0
  48. package/dist/chunks-ZQEFAZNC.js +10 -0
  49. package/dist/cli.js +228 -36
  50. package/dist/cli.js.map +1 -1
  51. package/dist/{detect-RI2MQ33K.js → detect-YEZX2XCF.js} +2 -2
  52. package/dist/{formula-RXVSQPXI.js → formula-KSNAEBZ2.js} +23 -232
  53. package/dist/formula-KSNAEBZ2.js.map +1 -0
  54. package/dist/{formula-5NKVS2LR.cjs → formula-SB3NHDUM.cjs} +30 -240
  55. package/dist/formula-SB3NHDUM.cjs.map +1 -0
  56. package/dist/{formula-JCNF43NE.js → formula-SXC6LRRC.js} +23 -233
  57. package/dist/formula-SXC6LRRC.js.map +1 -0
  58. package/dist/index.cjs +1280 -589
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +112 -4
  61. package/dist/index.d.ts +112 -4
  62. package/dist/index.js +929 -238
  63. package/dist/index.js.map +1 -1
  64. package/dist/mcp.js +278 -68
  65. package/dist/mcp.js.map +1 -1
  66. package/dist/models-KA4GLQK5.js +25 -0
  67. package/dist/parser-ND2YGQZR.js +800 -0
  68. package/dist/parser-ND2YGQZR.js.map +1 -0
  69. package/dist/parser-RBZHA6SY.js +804 -0
  70. package/dist/parser-RBZHA6SY.js.map +1 -0
  71. package/dist/parser-WDZOMDG4.cjs +803 -0
  72. package/dist/parser-WDZOMDG4.cjs.map +1 -0
  73. package/dist/pdf-ocr-2Z7VZIFI.cjs +439 -0
  74. package/dist/pdf-ocr-2Z7VZIFI.cjs.map +1 -0
  75. package/dist/pdf-ocr-FJFXFADQ.js +438 -0
  76. package/dist/pdf-ocr-FJFXFADQ.js.map +1 -0
  77. package/dist/pdf-ocr-VYE4DHD4.js +386 -0
  78. package/dist/pdf-ocr-VYE4DHD4.js.map +1 -0
  79. package/dist/{profile-io-SLN4P76T.js → profile-io-TWHDIVPL.js} +3 -3
  80. package/dist/rasterize-W2NEZUSW.js +40 -0
  81. package/dist/rasterize-W2NEZUSW.js.map +1 -0
  82. package/dist/redact-7ILEAUTS.js +12 -0
  83. package/dist/redact-7ILEAUTS.js.map +1 -0
  84. package/dist/render-4MG5QUDG.js +10 -0
  85. package/dist/render-4MG5QUDG.js.map +1 -0
  86. package/dist/seal-D2QNXJVO.js +10 -0
  87. package/dist/seal-D2QNXJVO.js.map +1 -0
  88. package/dist/{setup-57FB3LSP.js → setup-Q2PRE7UA.js} +5 -3
  89. package/dist/setup-Q2PRE7UA.js.map +1 -0
  90. package/dist/{watch-OHQWSVPE.js → watch-VWXJZT2R.js} +78 -23
  91. package/dist/watch-VWXJZT2R.js.map +1 -0
  92. package/package.json +2 -1
  93. package/dist/chunk-2DZQF6YJ.js.map +0 -1
  94. package/dist/chunk-6XAAYAWL.cjs.map +0 -1
  95. package/dist/chunk-7S3M4N4E.js.map +0 -1
  96. package/dist/chunk-BNU5QGIZ.js.map +0 -1
  97. package/dist/chunk-D35VBACN.js.map +0 -1
  98. package/dist/chunk-MEPHGCPQ.js.map +0 -1
  99. package/dist/chunk-OQPILS7B.js.map +0 -1
  100. package/dist/chunk-UEIYETCQ.js.map +0 -1
  101. package/dist/formula-5NKVS2LR.cjs.map +0 -1
  102. package/dist/formula-JCNF43NE.js.map +0 -1
  103. package/dist/formula-RXVSQPXI.js.map +0 -1
  104. package/dist/parser-ITMU2WN5.cjs.map +0 -1
  105. package/dist/parser-KMMUS73Q.js.map +0 -1
  106. package/dist/parser-TCTCSBYZ.js.map +0 -1
  107. package/dist/provider-4ZJKV3DC.js +0 -37
  108. package/dist/provider-4ZJKV3DC.js.map +0 -1
  109. package/dist/provider-AKROB7WQ.js +0 -39
  110. package/dist/provider-AKROB7WQ.js.map +0 -1
  111. package/dist/provider-G4C2V2PD.cjs +0 -39
  112. package/dist/provider-G4C2V2PD.cjs.map +0 -1
  113. package/dist/render-25BT623I.js +0 -10
  114. package/dist/seal-R4TETA4C.js +0 -10
  115. package/dist/setup-57FB3LSP.js.map +0 -1
  116. package/dist/watch-OHQWSVPE.js.map +0 -1
  117. /package/dist/{-LE4RLXVA.js.map → -SXQIZHYA.js.map} +0 -0
  118. /package/dist/{chunk-RROH5WHO.js.map → chunk-PLIY23LY.js.map} +0 -0
  119. /package/dist/{detect-RI2MQ33K.js.map → chunks-ZQEFAZNC.js.map} +0 -0
  120. /package/dist/{profile-io-SLN4P76T.js.map → detect-YEZX2XCF.js.map} +0 -0
  121. /package/dist/{render-25BT623I.js.map → models-KA4GLQK5.js.map} +0 -0
  122. /package/dist/{seal-R4TETA4C.js.map → profile-io-TWHDIVPL.js.map} +0 -0
package/dist/mcp.js CHANGED
@@ -31,34 +31,42 @@ import {
31
31
  patchHwp,
32
32
  patchHwpx,
33
33
  unknownFontWarnings
34
- } from "./chunk-7S3M4N4E.js";
35
- import {
36
- blocksToMarkdown
37
- } from "./chunk-UEIYETCQ.js";
34
+ } from "./chunk-IGGRLWT5.js";
35
+ import "./chunk-7FCNOKOR.js";
36
+ import "./chunk-4VNLZ2GZ.js";
38
37
  import {
39
38
  detectFormat,
40
39
  detectZipFormat
41
- } from "./chunk-MEPHGCPQ.js";
40
+ } from "./chunk-YOO6ET6M.js";
42
41
  import "./chunk-MOL7MDBG.js";
43
- import "./chunk-D35VBACN.js";
44
- import "./chunk-2DZQF6YJ.js";
45
- import "./chunk-BNU5QGIZ.js";
42
+ import "./chunk-WQLHPOMJ.js";
43
+ import "./chunk-DZIXKL7E.js";
44
+ import "./chunk-ELOV2GIJ.js";
45
+ import {
46
+ blocksToMarkdown
47
+ } from "./chunk-CMN3SYZ3.js";
48
+ import "./chunk-6SHO3BNV.js";
49
+ import "./chunk-V6U6YS5B.js";
46
50
  import {
47
51
  KordocError,
48
52
  VERSION,
53
+ classifyError,
49
54
  sanitizeError,
50
55
  toArrayBuffer
51
- } from "./chunk-HMUEU7D7.js";
56
+ } from "./chunk-FSXLSHCW.js";
52
57
 
53
58
  // src/mcp.ts
54
59
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
55
60
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
56
61
  import { z } from "zod";
57
- import { readFileSync, writeFileSync, realpathSync, openSync, readSync, closeSync, statSync, mkdirSync } from "fs";
58
- import { resolve, isAbsolute, extname, dirname } from "path";
59
- var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".hwp", ".hwpx", ".pdf", ".xlsx", ".docx"]);
62
+ import { readFileSync, writeFileSync, realpathSync, openSync, readSync, closeSync, statSync, mkdirSync, existsSync } from "fs";
63
+ import { pathToFileURL } from "url";
64
+ import { resolve, isAbsolute, extname, dirname, basename } from "path";
65
+ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".hwp", ".hwpx", ".hml", ".pdf", ".xls", ".xlsx", ".docx"]);
66
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".bmp"]);
67
+ var PROFILE_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
60
68
  var MAX_FILE_SIZE = 500 * 1024 * 1024;
61
- function safePath(filePath) {
69
+ function safePath(filePath, allowedExts = ALLOWED_EXTENSIONS) {
62
70
  if (!filePath) throw new KordocError("\uD30C\uC77C \uACBD\uB85C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4");
63
71
  const resolved = resolve(filePath);
64
72
  let real;
@@ -71,12 +79,53 @@ function safePath(filePath) {
71
79
  }
72
80
  if (!isAbsolute(real)) throw new KordocError("\uC808\uB300 \uACBD\uB85C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4");
73
81
  const ext = extname(real).toLowerCase();
74
- if (!ALLOWED_EXTENSIONS.has(ext)) throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD655\uC7A5\uC790\uC785\uB2C8\uB2E4: ${ext} (\uD5C8\uC6A9: ${[...ALLOWED_EXTENSIONS].join(", ")})`);
82
+ if (!allowedExts.has(ext)) throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD655\uC7A5\uC790\uC785\uB2C8\uB2E4: ${ext} (\uD5C8\uC6A9: ${[...allowedExts].join(", ")})`);
75
83
  return real;
76
84
  }
85
+ function safeOutputPath(outputPath, allowedExts) {
86
+ if (!outputPath) throw new KordocError("\uCD9C\uB825 \uACBD\uB85C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4");
87
+ const resolved = resolve(outputPath);
88
+ const ext = extname(resolved).toLowerCase();
89
+ if (!allowedExts.has(ext)) {
90
+ throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uCD9C\uB825 \uD655\uC7A5\uC790\uC785\uB2C8\uB2E4: ${ext || "(\uC5C6\uC74C)"} (\uD5C8\uC6A9: ${[...allowedExts].join(", ")})`);
91
+ }
92
+ const parent = dirname(resolved);
93
+ if (existsSync(parent)) {
94
+ try {
95
+ return resolve(realpathSync(parent), basename(resolved));
96
+ } catch (err) {
97
+ throw new KordocError(`\uCD9C\uB825 \uACBD\uB85C \uCC98\uB9AC \uC624\uB958 [${err?.code ?? "UNKNOWN"}]: ${parent}`);
98
+ }
99
+ }
100
+ return resolved;
101
+ }
102
+ function describeError(err) {
103
+ if (err instanceof KordocError) return err.message;
104
+ const code = err?.code;
105
+ if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
106
+ const hints = {
107
+ ENOENT: "\uD30C\uC77C \uB610\uB294 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
108
+ EACCES: "\uC811\uADFC \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
109
+ EPERM: "\uC791\uC5C5 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
110
+ EISDIR: "\uD30C\uC77C\uC774 \uC544\uB2C8\uB77C \uB514\uB809\uD1A0\uB9AC\uC785\uB2C8\uB2E4",
111
+ ENOTDIR: "\uACBD\uB85C \uC911\uAC04\uC774 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4",
112
+ ENOSPC: "\uB514\uC2A4\uD06C \uACF5\uAC04\uC774 \uBD80\uC871\uD569\uB2C8\uB2E4"
113
+ };
114
+ return `\uD30C\uC77C \uC2DC\uC2A4\uD15C \uC624\uB958 [${code}]: ${hints[code] ?? "\uACBD\uB85C\uC640 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694"}`;
115
+ }
116
+ const cls = classifyError(err);
117
+ return cls === "PARSE_ERROR" ? sanitizeError(err) : `\uBB38\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4 (${cls})`;
118
+ }
119
+ var MAX_RESPONSE_CHARS = 2e5;
120
+ function capResponseText(text, maxChars = MAX_RESPONSE_CHARS) {
121
+ if (text.length <= maxChars) return text;
122
+ return text.slice(0, maxChars) + `
123
+
124
+ \u2026 [\uC751\uB2F5\uC774 ${maxChars.toLocaleString()}\uC790 \uC0C1\uD55C\uC744 \uB118\uC5B4 \uC808\uB2E8\uB428 (\uC804\uCCB4 ${text.length.toLocaleString()}\uC790) \u2014 parse_pages\uB85C \uD398\uC774\uC9C0 \uBC94\uC704\uB97C \uB098\uB220 \uC77D\uC73C\uC138\uC694]`;
125
+ }
77
126
  var MAX_METADATA_FILE_SIZE = 50 * 1024 * 1024;
78
- function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE) {
79
- const resolved = safePath(filePath);
127
+ function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE, allowedExts = ALLOWED_EXTENSIONS) {
128
+ const resolved = safePath(filePath, allowedExts);
80
129
  let fileSize;
81
130
  try {
82
131
  fileSize = statSync(resolved).size;
@@ -97,9 +146,9 @@ function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE) {
97
146
  function detectFormatFromHeader(resolved) {
98
147
  const fd = openSync(resolved, "r");
99
148
  try {
100
- const headerBuf = Buffer.alloc(16);
101
- readSync(fd, headerBuf, 0, 16, 0);
102
- return detectFormat(toArrayBuffer(headerBuf));
149
+ const headerBuf = Buffer.alloc(512);
150
+ const bytesRead = readSync(fd, headerBuf, 0, 512, 0);
151
+ return detectFormat(toArrayBuffer(headerBuf.subarray(0, bytesRead)));
103
152
  } finally {
104
153
  closeSync(fd);
105
154
  }
@@ -112,11 +161,12 @@ server.tool(
112
161
  "parse_document",
113
162
  "\uD55C\uAD6D \uBB38\uC11C \uD30C\uC77C(HWP, HWPX, PDF, XLSX, DOCX)\uC744 \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. \uD30C\uC77C \uACBD\uB85C\uB97C \uC785\uB825\uD558\uBA74 \uD3EC\uB9F7\uC744 \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD569\uB2C8\uB2E4.",
114
163
  {
115
- file_path: z.string().min(1).describe("\uD30C\uC2F1\uD560 \uBB38\uC11C \uD30C\uC77C\uC758 \uC808\uB300 \uACBD\uB85C (HWP, HWPX, PDF, XLSX, DOCX)")
164
+ file_path: z.string().min(1).describe("\uD30C\uC2F1\uD560 \uBB38\uC11C \uD30C\uC77C\uC758 \uC808\uB300 \uACBD\uB85C (HWP, HWPX, PDF, XLSX, DOCX)"),
165
+ ocr: z.boolean().optional().describe("\uC2A4\uCE94/\uC774\uBBF8\uC9C0 PDF \uD14D\uC2A4\uD2B8 OCR (\uB0B4\uC7A5 PP-OCRv5 korean, \uCCAB \uC0AC\uC6A9 \uC2DC ~18MB \uC790\uB3D9 \uB2E4\uC6B4\uB85C\uB4DC). \uD14D\uC2A4\uD2B8\uCE35\uC774 \uC5C6\uAC70\uB098 \uAE68\uC9C4 \uD398\uC774\uC9C0\uB9CC \uC778\uC2DD\uD558\uACE0 \uC815\uC0C1 \uD398\uC774\uC9C0\uB294 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4. parse \uACB0\uACFC\uC5D0 NEEDS_OCR \uACBD\uACE0\uAC00 \uC788\uC73C\uBA74 \uC774 \uC635\uC158\uC73C\uB85C \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694")
116
166
  },
117
- async ({ file_path }) => {
167
+ async ({ file_path, ocr }) => {
118
168
  try {
119
- const { buffer } = readValidatedFile(file_path);
169
+ const { buffer, resolved } = readValidatedFile(file_path);
120
170
  const format = detectFormat(buffer);
121
171
  if (format === "unknown") {
122
172
  return {
@@ -124,7 +174,7 @@ server.tool(
124
174
  isError: true
125
175
  };
126
176
  }
127
- const result = await parse(buffer);
177
+ const result = await parse(buffer, { filePath: resolved, ...ocr ? { ocr: true } : {} });
128
178
  if (!result.success) {
129
179
  return {
130
180
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328 (${result.fileType}): ${result.error}` }],
@@ -137,7 +187,7 @@ server.tool(
137
187
  result.pageCount ? `\uD398\uC774\uC9C0: ${result.pageCount}` : null,
138
188
  result.metadata?.title ? `\uC81C\uBAA9: ${result.metadata.title}` : null,
139
189
  result.metadata?.author ? `\uC791\uC131\uC790: ${result.metadata.author}` : null,
140
- result.isImageBased ? "\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (\uD14D\uC2A4\uD2B8 \uCD94\uCD9C \uBD88\uAC00)" : null
190
+ result.isImageBased ? result.warnings?.some((w) => w.code === "OCR_APPLIED") ? "\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (OCR \uC801\uC6A9)" : "\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (\uD14D\uC2A4\uD2B8 \uCD94\uCD9C \uBD88\uAC00 \u2014 ocr: true \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5)" : null
141
191
  ].filter(Boolean).join(" | ");
142
192
  const parts = [`[${meta}]`];
143
193
  if (result.outline && result.outline.length > 0) {
@@ -156,11 +206,11 @@ ${warnText}`);
156
206
 
157
207
  ${markdown}`);
158
208
  return {
159
- content: [{ type: "text", text: parts.join("") }]
209
+ content: [{ type: "text", text: capResponseText(parts.join("")) }]
160
210
  };
161
211
  } catch (err) {
162
212
  return {
163
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
213
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
164
214
  isError: true
165
215
  };
166
216
  }
@@ -177,7 +227,7 @@ server.tool(
177
227
  const resolved = safePath(file_path);
178
228
  let format = detectFormatFromHeader(resolved);
179
229
  if (format === "hwpx") {
180
- const { buffer } = readValidatedFile(file_path, MAX_METADATA_FILE_SIZE);
230
+ const { buffer } = readValidatedFile(file_path);
181
231
  format = await detectZipFormat(buffer);
182
232
  }
183
233
  return {
@@ -185,7 +235,7 @@ server.tool(
185
235
  };
186
236
  } catch (err) {
187
237
  return {
188
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
238
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
189
239
  isError: true
190
240
  };
191
241
  }
@@ -211,7 +261,7 @@ server.tool(
211
261
  let metadata;
212
262
  let effectiveFormat = format;
213
263
  if (format === "hwpx") {
214
- const { detectZipFormat: detectZipFormat2 } = await import("./detect-RI2MQ33K.js");
264
+ const { detectZipFormat: detectZipFormat2 } = await import("./detect-YEZX2XCF.js");
215
265
  const zipFormat = await detectZipFormat2(buffer);
216
266
  if (zipFormat === "xlsx" || zipFormat === "docx") effectiveFormat = zipFormat;
217
267
  }
@@ -224,12 +274,14 @@ server.tool(
224
274
  break;
225
275
  case "pdf":
226
276
  try {
227
- const { extractPdfMetadataOnly } = await import("./parser-TCTCSBYZ.js");
277
+ const { extractPdfMetadataOnly } = await import("./parser-RBZHA6SY.js");
228
278
  metadata = await extractPdfMetadataOnly(buffer);
229
279
  } catch {
230
280
  metadata = void 0;
231
281
  }
232
282
  break;
283
+ case "hwp3":
284
+ case "hwpml":
233
285
  case "xlsx":
234
286
  case "docx": {
235
287
  const result = await parse(buffer);
@@ -242,7 +294,7 @@ server.tool(
242
294
  };
243
295
  } catch (err) {
244
296
  return {
245
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
297
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
246
298
  isError: true
247
299
  };
248
300
  }
@@ -284,7 +336,7 @@ ${result.markdown}` }]
284
336
  };
285
337
  } catch (err) {
286
338
  return {
287
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
339
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
288
340
  isError: true
289
341
  };
290
342
  }
@@ -336,7 +388,7 @@ ${tableMarkdown}` }]
336
388
  };
337
389
  } catch (err) {
338
390
  return {
339
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
391
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
340
392
  isError: true
341
393
  };
342
394
  }
@@ -371,7 +423,7 @@ server.tool(
371
423
  };
372
424
  } catch (err) {
373
425
  return {
374
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
426
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
375
427
  isError: true
376
428
  };
377
429
  }
@@ -386,7 +438,7 @@ server.tool(
386
438
  async ({ file_path }) => {
387
439
  try {
388
440
  const { buffer } = readValidatedFile(file_path);
389
- const result = await parse(buffer);
441
+ const result = await parse(buffer, { keepTrailingEmptyCols: true });
390
442
  if (!result.success) {
391
443
  return {
392
444
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${result.error}` }],
@@ -399,7 +451,7 @@ server.tool(
399
451
  };
400
452
  } catch (err) {
401
453
  return {
402
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
454
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
403
455
  isError: true
404
456
  };
405
457
  }
@@ -427,6 +479,8 @@ server.tool(
427
479
  },
428
480
  async ({ file_path, fields, formats, require_unique, mask_values, output_format, output_path }) => {
429
481
  try {
482
+ const outExts = output_format === "markdown" ? /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]) : /* @__PURE__ */ new Set([".hwpx"]);
483
+ const outPath = output_path ? safeOutputPath(output_path, outExts) : void 0;
430
484
  const { buffer } = readValidatedFile(file_path);
431
485
  if (output_format === "hwpx-preserve") {
432
486
  const format = detectFormat(buffer);
@@ -458,16 +512,16 @@ server.tool(
458
512
  verifyLine
459
513
  ].filter(Boolean).join(" | ");
460
514
  const filledList = hwpxResult.filled.map((f) => ` - ${f.label}: ${mask_values ? `[${[...f.value].length}\uC790]` : f.value}`).join("\n");
461
- if (output_path) {
462
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
463
- writeFileSync(resolve(output_path), Buffer.from(hwpxResult.buffer));
515
+ if (outPath) {
516
+ mkdirSync(dirname(outPath), { recursive: true });
517
+ writeFileSync(outPath, Buffer.from(hwpxResult.buffer));
464
518
  return {
465
519
  content: [{ type: "text", text: `[${summary2}]
466
520
 
467
521
  \uCC44\uC6CC\uC9C4 \uD544\uB4DC:
468
522
  ${filledList}
469
523
 
470
- HWPX \uD30C\uC77C \uC800\uC7A5 (\uC6D0\uBCF8 \uC11C\uC2DD \uC720\uC9C0): ${resolve(output_path)}` }]
524
+ HWPX \uD30C\uC77C \uC800\uC7A5 (\uC6D0\uBCF8 \uC11C\uC2DD \uC720\uC9C0): ${outPath}` }]
471
525
  };
472
526
  }
473
527
  return {
@@ -479,7 +533,7 @@ ${filledList}
479
533
  \u26A0\uFE0F output_path\uB97C \uC9C0\uC815\uD558\uBA74 \uC6D0\uBCF8 \uC11C\uC2DD\uC774 \uC720\uC9C0\uB41C HWPX \uD30C\uC77C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4.` }]
480
534
  };
481
535
  }
482
- const result = await parse(buffer);
536
+ const result = await parse(buffer, { keepTrailingEmptyCols: true });
483
537
  if (!result.success) {
484
538
  return {
485
539
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${result.error}` }],
@@ -505,13 +559,13 @@ ${filledList}
505
559
  ].filter(Boolean).join(" | ");
506
560
  if (output_format === "hwpx") {
507
561
  const hwpxBuffer = await markdownToHwpx(markdown);
508
- if (output_path) {
509
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
510
- writeFileSync(resolve(output_path), Buffer.from(hwpxBuffer));
562
+ if (outPath) {
563
+ mkdirSync(dirname(outPath), { recursive: true });
564
+ writeFileSync(outPath, Buffer.from(hwpxBuffer));
511
565
  return {
512
566
  content: [{ type: "text", text: `[${summary}]
513
567
 
514
- HWPX \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}` }]
568
+ HWPX \uD30C\uC77C \uC800\uC7A5: ${outPath}` }]
515
569
  };
516
570
  }
517
571
  return {
@@ -522,13 +576,13 @@ HWPX \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}` }]
522
576
  ${previewMd}` }]
523
577
  };
524
578
  }
525
- if (output_path) {
526
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
527
- writeFileSync(resolve(output_path), markdown, "utf-8");
579
+ if (outPath) {
580
+ mkdirSync(dirname(outPath), { recursive: true });
581
+ writeFileSync(outPath, markdown, "utf-8");
528
582
  return {
529
583
  content: [{ type: "text", text: `[${summary}]
530
584
 
531
- \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}
585
+ \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uC800\uC7A5: ${outPath}
532
586
 
533
587
  ${previewMd}` }]
534
588
  };
@@ -540,7 +594,7 @@ ${previewMd}` }]
540
594
  };
541
595
  } catch (err) {
542
596
  return {
543
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
597
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
544
598
  isError: true
545
599
  };
546
600
  }
@@ -562,6 +616,7 @@ server.tool(
562
616
  },
563
617
  async ({ file_path, image_path, anchor, occurrence, size_mm, mode, dx_mm, dy_mm, output_path }) => {
564
618
  try {
619
+ const outPath = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx"]));
565
620
  const { buffer } = readValidatedFile(file_path);
566
621
  const format = detectFormat(buffer);
567
622
  if (format !== "hwpx") {
@@ -570,13 +625,13 @@ server.tool(
570
625
  isError: true
571
626
  };
572
627
  }
573
- const imgResolved = resolve(image_path);
628
+ const imgResolved = safePath(image_path, IMAGE_EXTENSIONS);
574
629
  if (statSync(imgResolved).size > 500 * 1024 * 1024) {
575
630
  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 };
576
631
  }
577
632
  const image = new Uint8Array(readFileSync(imgResolved));
578
- const ext = extname(image_path).slice(1).toLowerCase() || "png";
579
- const { placeSealHwpx } = await import("./seal-R4TETA4C.js");
633
+ const ext = extname(imgResolved).slice(1).toLowerCase();
634
+ const { placeSealHwpx } = await import("./seal-D2QNXJVO.js");
580
635
  const result = await placeSealHwpx(buffer, [{
581
636
  anchor,
582
637
  occurrence,
@@ -587,8 +642,8 @@ server.tool(
587
642
  dxMm: dx_mm,
588
643
  dyMm: dy_mm
589
644
  }]);
590
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
591
- writeFileSync(resolve(output_path), Buffer.from(result.buffer));
645
+ mkdirSync(dirname(outPath), { recursive: true });
646
+ writeFileSync(outPath, Buffer.from(result.buffer));
592
647
  const p0 = result.placed[0];
593
648
  const warnLines = (p0.warnings ?? []).map((w) => `
594
649
  \u26A0\uFE0F ${w}`).join("");
@@ -596,13 +651,13 @@ server.tool(
596
651
  content: [{
597
652
  type: "text",
598
653
  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})
599
- \uC800\uC7A5: ${resolve(output_path)}${warnLines}
654
+ \uC800\uC7A5: ${outPath}${warnLines}
600
655
  \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.`
601
656
  }]
602
657
  };
603
658
  } catch (err) {
604
659
  return {
605
- content: [{ type: "text", text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC2E4\uD328: ${sanitizeError(err)}` }],
660
+ content: [{ type: "text", text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC2E4\uD328: ${describeError(err)}` }],
606
661
  isError: true
607
662
  };
608
663
  }
@@ -618,6 +673,7 @@ server.tool(
618
673
  },
619
674
  async ({ file_path, edited_markdown, output_path }) => {
620
675
  try {
676
+ const out = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx", ".hwp"]));
621
677
  const { buffer } = readValidatedFile(file_path);
622
678
  const format = detectFormat(buffer);
623
679
  let isHwpx = format === "hwpx";
@@ -639,7 +695,6 @@ server.tool(
639
695
  isError: true
640
696
  };
641
697
  }
642
- const out = resolve(output_path);
643
698
  mkdirSync(dirname(out), { recursive: true });
644
699
  writeFileSync(out, Buffer.from(result.data));
645
700
  const v = result.verification?.stats;
@@ -655,12 +710,147 @@ server.tool(
655
710
  };
656
711
  } catch (err) {
657
712
  return {
658
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
713
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
714
+ isError: true
715
+ };
716
+ }
717
+ }
718
+ );
719
+ server.tool(
720
+ "render_document",
721
+ "HWPX \uBB38\uC11C\uB97C \uC2E4\uC81C \uC870\uD310 \uADF8\uB300\uB85C \uB80C\uB354\uD574 PNG \uC774\uBBF8\uC9C0(\uB610\uB294 SVG \uD30C\uC77C)\uB85C \uB3CC\uB824\uC90D\uB2C8\uB2E4. generate_document\xB7fill_form\xB7patch_document\xB7place_seal \uACB0\uACFC\uBB3C\uC744 \uB208\uC73C\uB85C \uD655\uC778\uD558\uB294 \uC6A9\uB3C4 \u2014 \uC0DD\uC131/\uC218\uC815 \uD6C4 \uC774 \uB3C4\uAD6C\uB85C \uB80C\uB354\uD574 \uAE68\uC9D0\xB7\uC798\uB9BC\xB7\uBC30\uCE58\uB97C \uAC80\uC99D\uD558\uACE0 \uB2E4\uC2DC \uACE0\uCE58\uB294 \uB8E8\uD504\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4. \uD55C\uCEF4 \uC800\uC7A5\uBCF8\uC740 \uC870\uD310 \uCE90\uC2DC\uB85C, AI \uC0DD\uC131\uBCF8(\uCE90\uC2DC \uC5C6\uC74C)\uC740 \uC21C\uC218 \uC870\uD310 \uC5D4\uC9C4(reflow)\uC73C\uB85C \uB80C\uB354\uB418\uBA70 \uD6C4\uC790\uB294 \uD55C\uCEF4 \uC2E4\uC870\uD310\uC758 \uADFC\uC0AC\uC785\uB2C8\uB2E4 (\uCC38\uACE0\uC6A9 \uBBF8\uB9AC\uBCF4\uAE30).",
722
+ {
723
+ file_path: z.string().min(1).describe("\uB80C\uB354\uD560 HWPX \uD30C\uC77C\uC758 \uC808\uB300 \uACBD\uB85C"),
724
+ format: z.enum(["png", "svg"]).default("png").describe("png=\uC774\uBBF8\uC9C0\uB85C \uC751\uB2F5\uC5D0 \uC9C1\uC811 \uBC18\uD658 (sharp \uD544\uC694, \uAE30\uBCF8 \uC124\uCE58\uB428) / svg=output_path\uC5D0 \uD30C\uC77C \uC800\uC7A5"),
725
+ output_path: z.string().min(1).optional().describe("\uACB0\uACFC \uC800\uC7A5 \uACBD\uB85C (png\uB294 \uC120\uD0DD, svg\uB294 \uD544\uC218 \u2014 \uD655\uC7A5\uC790\uB294 format\uACFC \uC77C\uCE58)"),
726
+ highlights: z.array(z.string().min(1)).optional().describe("\uD615\uAD11\uD39C \uD45C\uC2DC\uD560 \uAC80\uC0C9\uC5B4 \uBAA9\uB85D \u2014 \uCC44\uC6B4 \uAC12\xB7\uC218\uC815 \uBB38\uAD6C \uC704\uCE58 \uD655\uC778\uC6A9"),
727
+ reflow_mode: z.enum(["keep", "charAll"]).default("keep").describe("reflow \uC904\uBC14\uAFC8: keep=\uC5B4\uC808 \uB2E8\uC704, charAll=\uAE00\uC790 \uB2E8\uC704")
728
+ },
729
+ async ({ file_path, format, output_path, highlights, reflow_mode }) => {
730
+ try {
731
+ if (format === "svg" && !output_path) {
732
+ return { content: [{ type: "text", text: 'format: "svg"\uB294 output_path(.svg)\uAC00 \uD544\uC218\uC785\uB2C8\uB2E4 \u2014 SVG \uC6D0\uBB38\uC740 \uCEE4\uC11C \uC751\uB2F5\uC5D0 \uC9C1\uC811 \uB2F4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.' }], isError: true };
733
+ }
734
+ const outPath = output_path ? safeOutputPath(output_path, /* @__PURE__ */ new Set([format === "png" ? ".png" : ".svg"])) : void 0;
735
+ const { buffer } = readValidatedFile(file_path, MAX_FILE_SIZE, /* @__PURE__ */ new Set([".hwpx"]));
736
+ const { renderHwpxToSvg } = await import("./render-4MG5QUDG.js");
737
+ const result = await renderHwpxToSvg(buffer, { highlights, reflow: true, reflowMode: reflow_mode });
738
+ const summary = [
739
+ `\uB80C\uB354 \uC644\uB8CC: ${result.pageCount}\uD398\uC774\uC9C0, ${Math.round(result.width)}x${Math.round(result.height)}pt (\uD14D\uC2A4\uD2B8 ${result.stats.texts}\xB7\uC774\uBBF8\uC9C0 ${result.stats.images}\xB7\uD45C ${result.stats.tables})`,
740
+ ...result.warnings.map((w) => `\u26A0\uFE0F ${w}`)
741
+ ];
742
+ if (format === "svg") {
743
+ mkdirSync(dirname(outPath), { recursive: true });
744
+ writeFileSync(outPath, result.svg, "utf-8");
745
+ summary.push(`\uC800\uC7A5: ${outPath}`);
746
+ return { content: [{ type: "text", text: summary.join("\n") }] };
747
+ }
748
+ const { rasterizeSvg } = await import("./rasterize-W2NEZUSW.js");
749
+ const raster = await rasterizeSvg(result.svg, result.width, result.height);
750
+ if (outPath) {
751
+ mkdirSync(dirname(outPath), { recursive: true });
752
+ writeFileSync(outPath, raster.png);
753
+ summary.push(`\uC800\uC7A5: ${outPath}`);
754
+ }
755
+ summary.push(`\uC774\uBBF8\uC9C0 ${raster.widthPx}x${raster.heightPx}px \u2014 \uC798\uB9BC\xB7\uACB9\uCE68\xB7\uBE48\uCE78\xB7\uD398\uC774\uC9C0 \uB118\uCE68\uC774 \uBCF4\uC774\uBA74 \uC6D0\uC778 \uD14D\uC2A4\uD2B8\uB97C \uC218\uC815\uD574 \uB2E4\uC2DC \uC0DD\uC131/\uD328\uCE58\uD558\uC138\uC694.`);
756
+ return {
757
+ content: [
758
+ { type: "image", data: raster.png.toString("base64"), mimeType: "image/png" },
759
+ { type: "text", text: summary.join("\n") }
760
+ ]
761
+ };
762
+ } catch (err) {
763
+ return {
764
+ content: [{ type: "text", text: `\uB80C\uB354 \uC2E4\uD328: ${describeError(err)}` }],
659
765
  isError: true
660
766
  };
661
767
  }
662
768
  }
663
769
  );
770
+ server.tool(
771
+ "redact_document",
772
+ "\uBB38\uC11C\uC758 \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uBC88\uD638\xB7\uC804\uD654\xB7\uC774\uBA54\uC77C\xB7\uCE74\uB4DC\xB7\uACC4\uC88C)\uB97C \uD0D0\uC9C0\uD574 \uC11C\uC2DD \uBCF4\uC874 \uB9C8\uC2A4\uD0B9\uD569\uB2C8\uB2E4. HWPX/HWP\uB294 \uC6D0\uBCF8 \uC11C\uC2DD 1\uBC14\uC774\uD2B8 \uADF8\uB300\uB85C patch, \uADF8 \uC678 \uD3EC\uB9F7\uC740 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uCD9C\uB825. \uC790\uB3D9 \uAC80\uCD9C \uBCF4\uC870 \uB3C4\uAD6C \u2014 \uACB0\uACFC \uB9AC\uD3EC\uD2B8\uB97C \uC0AC\uB78C\uC774 \uCD5C\uC885 \uD655\uC778\uD574\uC57C \uD558\uBA70 \uC774\uBBF8\uC9C0 \uC548 \uD14D\uC2A4\uD2B8\uB294 \uD0D0\uC9C0\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4. \uB9C8\uC2A4\uD0B9 \uD6C4 render_document\uB85C \uB208\uC73C\uB85C \uD655\uC778\uD558\uB294 \uAC83\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
773
+ {
774
+ file_path: z.string().min(1).describe("\uB300\uC0C1 \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C"),
775
+ rules: z.array(z.enum(["rrn", "phone", "email", "card", "account", "passport", "driver"])).optional().describe("\uC801\uC6A9 \uB8F0 (\uAE30\uBCF8: rrn\xB7phone\xB7email\xB7card\xB7account \u2014 passport\xB7driver\uB294 opt-in)"),
776
+ mask_char: z.string().min(1).max(1).optional().describe("\uB9C8\uC2A4\uD06C \uBB38\uC790 1\uAE00\uC790 (\uAE30\uBCF8: \u25CF)"),
777
+ output_path: z.string().min(1).optional().describe("\uCD9C\uB825 \uACBD\uB85C (HWPX/HWP\uB294 \uAC19\uC740 \uD655\uC7A5\uC790, \uADF8 \uC678\uB294 .md) \u2014 dry_run\uC774 \uC544\uB2C8\uBA74 \uD544\uC218"),
778
+ dry_run: z.boolean().default(false).describe("\uD0D0\uC9C0 \uB9AC\uD3EC\uD2B8\uB9CC \uBC18\uD658, \uD30C\uC77C \uBBF8\uC0DD\uC131")
779
+ },
780
+ async ({ file_path, rules, mask_char, output_path, dry_run }) => {
781
+ try {
782
+ const { buffer } = readValidatedFile(file_path);
783
+ const format = detectFormat(buffer);
784
+ const patchable = format === "hwpx" || format === "hwp";
785
+ if (!dry_run && !output_path) {
786
+ return { content: [{ type: "text", text: "output_path\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uD0D0\uC9C0\uB9CC \uC6D0\uD558\uBA74 dry_run: true)." }], isError: true };
787
+ }
788
+ const outPath = !dry_run ? safeOutputPath(output_path, new Set(patchable ? [format === "hwp" ? ".hwp" : ".hwpx"] : [".md", ".markdown", ".txt"])) : void 0;
789
+ const parsed = await parse(buffer, { filePath: file_path });
790
+ if (!parsed.success) {
791
+ return { content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}` }], isError: true };
792
+ }
793
+ const { redactMarkdown } = await import("./redact-7ILEAUTS.js");
794
+ const r = redactMarkdown(parsed.markdown, { rules, maskChar: mask_char });
795
+ const byRule = /* @__PURE__ */ new Map();
796
+ for (const h of r.hits) byRule.set(h.rule, (byRule.get(h.rule) ?? 0) + 1);
797
+ const lines = [
798
+ `\uD0D0\uC9C0: ${r.hits.length}\uAC74 (${[...byRule.entries()].map(([k, v]) => `${k} ${v}`).join(", ") || "\uC5C6\uC74C"})`,
799
+ ...r.hits.map((h) => ` - [${h.rule}] ${h.masked}`)
800
+ ];
801
+ if (!dry_run && r.hits.length > 0) {
802
+ if (patchable) {
803
+ const original = new Uint8Array(buffer);
804
+ const result = format === "hwp" ? await patchHwp(original, r.text) : await patchHwpx(original, r.text);
805
+ if (!result.success || !result.data) {
806
+ return { content: [{ type: "text", text: `\uB9C8\uC2A4\uD0B9 \uD328\uCE58 \uC2E4\uD328: ${result.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}` }], isError: true };
807
+ }
808
+ mkdirSync(dirname(outPath), { recursive: true });
809
+ writeFileSync(outPath, Buffer.from(result.data));
810
+ lines.push(`\uC800\uC7A5: ${outPath} (\uC6D0\uBCF8 \uC11C\uC2DD \uBCF4\uC874)`);
811
+ if (result.skipped.length > 0) {
812
+ lines.push(`\u26A0\uFE0F \uBBF8\uC801\uC6A9 ${result.skipped.length}\uAC74 \u2014 \uD574\uB2F9 \uC704\uCE58\uB294 \uC6D0\uBB38\uC774 \uB0A8\uC544 \uC788\uC73C\uB2C8 \uBC18\uB4DC\uC2DC \uC218\uB3D9 \uD655\uC778:`);
813
+ for (const s of result.skipped) lines.push(` - ${s.reason}`);
814
+ }
815
+ lines.push("render_document\uB85C \uB9C8\uC2A4\uD0B9 \uACB0\uACFC\uB97C \uB208\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.");
816
+ } else {
817
+ mkdirSync(dirname(outPath), { recursive: true });
818
+ writeFileSync(outPath, r.text, "utf-8");
819
+ lines.push(`\uC800\uC7A5: ${outPath} (${format}\uB294 \uC11C\uC2DD \uBCF4\uC874 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4)`);
820
+ }
821
+ } else if (!dry_run) {
822
+ lines.push("\uD0D0\uC9C0 0\uAC74 \u2014 \uCD9C\uB825 \uD30C\uC77C\uC744 \uB9CC\uB4E4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
823
+ }
824
+ lines.push("\uC8FC\uC758: \uC790\uB3D9 \uAC80\uCD9C\uC740 \uBCF4\uC870 \uC218\uB2E8\uC785\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC18D \uD14D\uC2A4\uD2B8\xB7\uD45C\uAE30 \uBCC0\uD615\uC740 \uB193\uCE60 \uC218 \uC788\uC73C\uB2C8 \uCD5C\uC885 \uACF5\uAC1C \uC804 \uC0AC\uB78C \uAC80\uD1A0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
825
+ return { content: [{ type: "text", text: capResponseText(lines.join("\n")) }] };
826
+ } catch (err) {
827
+ return { content: [{ type: "text", text: `\uB9C8\uC2A4\uD0B9 \uC2E4\uD328: ${describeError(err)}` }], isError: true };
828
+ }
829
+ }
830
+ );
831
+ server.tool(
832
+ "parse_chunks",
833
+ "\uBB38\uC11C\uB97C RAG\uC6A9 \uAD6C\uC870 \uCCAD\uD06C JSON\uC73C\uB85C \uD30C\uC2F1\uD569\uB2C8\uB2E4. \uD5E4\uB529\xB7\uAC1C\uC870\uC2DD \uC704\uACC4(\u25A1\u25CB- / 1.\xB7\uAC00.\xB71))\uAC00 breadcrumb \uACBD\uB85C\uB85C \uBCF4\uC874\uB418\uACE0 \uD45C\uB294 \uB3C5\uB9BD \uCCAD\uD06C\uB85C \uB098\uC635\uB2C8\uB2E4 \u2014 \uC784\uBCA0\uB529\xB7\uC778\uB371\uC2F1 \uC804\uCC98\uB9AC\uC6A9. \uC790\uB974\uAE30(\uD1A0\uD070 \uC0C1\uD55C\xB7\uC624\uBC84\uB7A9)\uB294 \uC18C\uBE44\uC790 \uBAAB\uC785\uB2C8\uB2E4.",
834
+ {
835
+ file_path: z.string().min(1).describe("\uB300\uC0C1 \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C (HWP/HWPX/PDF/XLSX/DOCX)"),
836
+ granularity: z.enum(["section", "block"]).default("section").describe("section=\uAC19\uC740 breadcrumb \uC544\uB798 \uC5F0\uC18D \uD14D\uC2A4\uD2B8 \uBCD1\uD569(\uAE30\uBCF8), block=IRBlock 1\uAC1C=\uCCAD\uD06C 1\uAC1C"),
837
+ include_table_cells: z.boolean().default(false).describe("\uD45C \uCCAD\uD06C\uC5D0 \uC140 \uD14D\uC2A4\uD2B8 2\uCC28\uC6D0 \uBC30\uC5F4 \uD3EC\uD568 \uC5EC\uBD80")
838
+ },
839
+ async ({ file_path, granularity, include_table_cells }) => {
840
+ try {
841
+ const { buffer } = readValidatedFile(file_path);
842
+ const parsed = await parse(buffer, { filePath: file_path });
843
+ if (!parsed.success) {
844
+ return { content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}` }], isError: true };
845
+ }
846
+ const { blocksToChunks } = await import("./chunks-ZQEFAZNC.js");
847
+ const chunks = blocksToChunks(parsed.blocks, { granularity, includeTableCells: include_table_cells });
848
+ return { content: [{ type: "text", text: capResponseText(JSON.stringify(chunks, null, 2)) }] };
849
+ } catch (err) {
850
+ return { content: [{ type: "text", text: `\uCCAD\uD06C \uD30C\uC2F1 \uC2E4\uD328: ${describeError(err)}` }], isError: true };
851
+ }
852
+ }
853
+ );
664
854
  server.tool(
665
855
  "extract_profile",
666
856
  '\uCC38\uC870 HWPX \uBB38\uC11C\uC5D0\uC11C \uD45C \uC11C\uC2DD \uD504\uB85C\uD544(\uD14C\uB450\uB9AC\xB7\uC74C\uC601\xB7\uC5F4 \uB108\uBE44\xB7\uC140 \uAE00\uAF34)\uC744 JSON\uC73C\uB85C \uCD94\uCD9C\uD569\uB2C8\uB2E4. \uCD94\uCD9C\uD55C \uD504\uB85C\uD544\uC744 generate_document\uC758 profile_path\uB85C \uB118\uAE30\uBA74 \uC6D0\uBCF8 \uBB38\uC11C \uC5C6\uC774 \uAC19\uC740 \uD45C \uC11C\uC2DD\uC744 \uC7AC\uD604\uD569\uB2C8\uB2E4 \u2014 "\uC774 \uBB38\uC11C \uD45C \uC11C\uC2DD \uADF8\uB300\uB85C \uB9CC\uB4E4\uC5B4\uC918" \uC6CC\uD06C\uD50C\uB85C\uC758 1\uB2E8\uACC4.',
@@ -670,10 +860,10 @@ server.tool(
670
860
  },
671
861
  async ({ hwpx_path, output_path }) => {
672
862
  try {
863
+ const out = safeOutputPath(output_path, PROFILE_EXTENSIONS);
673
864
  const { buffer } = readValidatedFile(hwpx_path);
674
- const { hwpxToProfile } = await import("./-LE4RLXVA.js");
865
+ const { hwpxToProfile } = await import("./-SXQIZHYA.js");
675
866
  const profile = await hwpxToProfile(buffer);
676
- const out = resolve(output_path);
677
867
  mkdirSync(dirname(out), { recursive: true });
678
868
  writeFileSync(out, JSON.stringify(profile, null, 2));
679
869
  return {
@@ -682,7 +872,7 @@ server.tool(
682
872
  };
683
873
  } catch (err) {
684
874
  return {
685
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
875
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
686
876
  isError: true
687
877
  };
688
878
  }
@@ -755,11 +945,11 @@ server.tool(
755
945
  }
756
946
  let profile;
757
947
  if (profile_path) {
758
- const { parseFormatProfileJson } = await import("./profile-io-SLN4P76T.js");
759
- profile = parseFormatProfileJson(readFileSync(resolve(profile_path), "utf-8"));
948
+ const { parseFormatProfileJson } = await import("./profile-io-TWHDIVPL.js");
949
+ profile = parseFormatProfileJson(readFileSync(safePath(profile_path, PROFILE_EXTENSIONS), "utf-8"));
760
950
  }
951
+ const out = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx"]));
761
952
  const buf = await markdownToHwpx(markdown, gongmun || profile ? { ...gongmun ? { gongmun } : {}, ...profile ? { profile } : {} } : void 0);
762
- const out = resolve(output_path);
763
953
  mkdirSync(dirname(out), { recursive: true });
764
954
  writeFileSync(out, Buffer.from(buf));
765
955
  const mode = gongmun ? `\uACF5\uBB38\uC11C:${gongmun.preset}` : "\uBC94\uC6A9";
@@ -775,18 +965,38 @@ server.tool(
775
965
  };
776
966
  } catch (err) {
777
967
  return {
778
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
968
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
779
969
  isError: true
780
970
  };
781
971
  }
782
972
  }
783
973
  );
784
- async function main() {
974
+ var serverStarted = false;
975
+ async function startMcpServer() {
976
+ if (serverStarted) return;
977
+ serverStarted = true;
785
978
  const transport = new StdioServerTransport();
786
979
  await server.connect(transport);
787
980
  }
788
- main().catch((err) => {
981
+ var autoStart = true;
982
+ try {
983
+ const entry = process.argv[1];
984
+ if (entry && pathToFileURL(realpathSync(entry)).href !== import.meta.url) autoStart = false;
985
+ } catch {
986
+ }
987
+ if (autoStart) startMcpServer().catch((err) => {
789
988
  console.error(err);
790
989
  process.exit(1);
791
990
  });
991
+ export {
992
+ ALLOWED_EXTENSIONS,
993
+ IMAGE_EXTENSIONS,
994
+ PROFILE_EXTENSIONS,
995
+ buildFillInputs,
996
+ capResponseText,
997
+ describeError,
998
+ safeOutputPath,
999
+ safePath,
1000
+ startMcpServer
1001
+ };
792
1002
  //# sourceMappingURL=mcp.js.map