kordoc 4.0.7 → 4.1.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 (123) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +837 -806
  3. package/dist/{-6HWEFXVV.js → -AS4IABL2.js} +24 -11
  4. package/dist/{chunk-BKWHX3RC.js → chunk-37HJHCPA.js} +24 -15
  5. package/dist/chunk-37HJHCPA.js.map +1 -0
  6. package/dist/{chunk-U25XGCNH.cjs → chunk-37VKMUST.cjs} +174 -25
  7. package/dist/chunk-37VKMUST.cjs.map +1 -0
  8. package/dist/{chunk-5OKHAJ62.js → chunk-4KI23VHA.js} +162 -13
  9. package/dist/chunk-4KI23VHA.js.map +1 -0
  10. package/dist/{chunk-6GBLFQMA.js → chunk-5RPYBC2Q.js} +1 -1
  11. package/dist/chunk-5RPYBC2Q.js.map +1 -0
  12. package/dist/{chunk-NGSHDBQR.js → chunk-AX2R5Q2N.js} +7 -4
  13. package/dist/chunk-AX2R5Q2N.js.map +1 -0
  14. package/dist/{chunk-X34YWRL5.cjs → chunk-DCZVOIEO.cjs} +1 -1
  15. package/dist/chunk-DCZVOIEO.cjs.map +1 -0
  16. package/dist/chunk-DZIXKL7E.js +145 -0
  17. package/dist/chunk-DZIXKL7E.js.map +1 -0
  18. package/dist/chunk-F2ZU3IZG.js +82 -0
  19. package/dist/chunk-F2ZU3IZG.js.map +1 -0
  20. package/dist/{chunk-AIQ3ISQU.js → chunk-GE43BE46.js} +1 -1
  21. package/dist/chunk-GE43BE46.js.map +1 -0
  22. package/dist/{chunk-Q7EN4EUJ.js → chunk-GQVSHGG4.js} +12 -4
  23. package/dist/chunk-GQVSHGG4.js.map +1 -0
  24. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  25. package/dist/{chunk-TJJTLM76.js → chunk-L2XQQTOY.js} +1422 -1121
  26. package/dist/chunk-L2XQQTOY.js.map +1 -0
  27. package/dist/{chunk-3TBUDJDE.js → chunk-MOL7MDBG.js} +1 -1
  28. package/dist/chunk-MOL7MDBG.js.map +1 -0
  29. package/dist/{chunk-CNM75ZSX.js → chunk-O5VS7RIR.js} +2 -2
  30. package/dist/chunk-O5VS7RIR.js.map +1 -0
  31. package/dist/{chunk-2SN3CKME.js → chunk-SSTK6IKK.js} +18 -21
  32. package/dist/chunk-SSTK6IKK.js.map +1 -0
  33. package/dist/chunk-UOO7LSDG.js +152 -0
  34. package/dist/chunk-UOO7LSDG.js.map +1 -0
  35. package/dist/{chunk-ONXVBURQ.js → chunk-WO52HVQJ.js} +2 -2
  36. package/dist/chunk-WO52HVQJ.js.map +1 -0
  37. package/dist/{chunk-CFQLK3LM.js → chunk-YOO6ET6M.js} +7 -3
  38. package/dist/chunk-YOO6ET6M.js.map +1 -0
  39. package/dist/chunks-WVMGF7JL.js +10 -0
  40. package/dist/cli.js +173 -38
  41. package/dist/cli.js.map +1 -1
  42. package/dist/{detect-NTR2FUEG.js → detect-YEZX2XCF.js} +2 -2
  43. package/dist/{formula-ZYUDQHD3.cjs → formula-5NKVS2LR.cjs} +1 -1
  44. package/dist/formula-5NKVS2LR.cjs.map +1 -0
  45. package/dist/{formula-KOQV353G.js → formula-JCNF43NE.js} +1 -1
  46. package/dist/formula-JCNF43NE.js.map +1 -0
  47. package/dist/{formula-MEDGYQXS.js → formula-RXVSQPXI.js} +1 -1
  48. package/dist/formula-RXVSQPXI.js.map +1 -0
  49. package/dist/index.cjs +1932 -1397
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +106 -2
  52. package/dist/index.d.ts +106 -2
  53. package/dist/index.js +1626 -1091
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.js +275 -67
  56. package/dist/mcp.js.map +1 -1
  57. package/dist/page-range-737B4EZW.js +8 -0
  58. package/dist/page-range-LNMXJU6W.js +7 -0
  59. package/dist/page-range-P7SDW6LR.cjs +8 -0
  60. package/dist/page-range-P7SDW6LR.cjs.map +1 -0
  61. package/dist/{parser-E6U5TNAU.cjs → parser-N4A7UGDK.cjs} +314 -56
  62. package/dist/parser-N4A7UGDK.cjs.map +1 -0
  63. package/dist/{parser-5EHWYJMC.js → parser-PEOYBXBO.js} +287 -27
  64. package/dist/parser-PEOYBXBO.js.map +1 -0
  65. package/dist/{parser-UGB3NIVH.js → parser-XX4QTDFQ.js} +283 -25
  66. package/dist/parser-XX4QTDFQ.js.map +1 -0
  67. package/dist/{profile-io-CAKRPQRD.js → profile-io-PLDQFPJA.js} +3 -3
  68. package/dist/{provider-5K7O3Z2O.cjs → provider-4FAYHJ6N.cjs} +3 -3
  69. package/dist/provider-4FAYHJ6N.cjs.map +1 -0
  70. package/dist/{provider-H4WWSPOV.js → provider-C6IOGIS5.js} +3 -3
  71. package/dist/provider-C6IOGIS5.js.map +1 -0
  72. package/dist/{provider-7H4CPZYS.js → provider-UIBW2MIH.js} +3 -3
  73. package/dist/provider-UIBW2MIH.js.map +1 -0
  74. package/dist/rasterize-WWNQLKYW.js +40 -0
  75. package/dist/rasterize-WWNQLKYW.js.map +1 -0
  76. package/dist/redact-7ILEAUTS.js +12 -0
  77. package/dist/render-T7S6H6AN.js +10 -0
  78. package/dist/render-T7S6H6AN.js.map +1 -0
  79. package/dist/seal-7PTL6MXH.js +10 -0
  80. package/dist/seal-7PTL6MXH.js.map +1 -0
  81. package/dist/{setup-QSJ2INNS.js → setup-Q2PRE7UA.js} +5 -3
  82. package/dist/setup-Q2PRE7UA.js.map +1 -0
  83. package/dist/{watch-RMX427YR.js → watch-SFHXZALC.js} +78 -24
  84. package/dist/watch-SFHXZALC.js.map +1 -0
  85. package/package.json +97 -97
  86. package/dist/chunk-2SN3CKME.js.map +0 -1
  87. package/dist/chunk-3TBUDJDE.js.map +0 -1
  88. package/dist/chunk-5OKHAJ62.js.map +0 -1
  89. package/dist/chunk-6GBLFQMA.js.map +0 -1
  90. package/dist/chunk-AIQ3ISQU.js.map +0 -1
  91. package/dist/chunk-BKWHX3RC.js.map +0 -1
  92. package/dist/chunk-CFQLK3LM.js.map +0 -1
  93. package/dist/chunk-CNM75ZSX.js.map +0 -1
  94. package/dist/chunk-NGSHDBQR.js.map +0 -1
  95. package/dist/chunk-ONXVBURQ.js.map +0 -1
  96. package/dist/chunk-Q7EN4EUJ.js.map +0 -1
  97. package/dist/chunk-TJJTLM76.js.map +0 -1
  98. package/dist/chunk-U25XGCNH.cjs.map +0 -1
  99. package/dist/chunk-X34YWRL5.cjs.map +0 -1
  100. package/dist/formula-KOQV353G.js.map +0 -1
  101. package/dist/formula-MEDGYQXS.js.map +0 -1
  102. package/dist/formula-ZYUDQHD3.cjs.map +0 -1
  103. package/dist/page-range-CIRRJPXJ.js +0 -7
  104. package/dist/page-range-OF5I4PQY.js +0 -8
  105. package/dist/page-range-TPIRSA3J.cjs +0 -8
  106. package/dist/page-range-TPIRSA3J.cjs.map +0 -1
  107. package/dist/parser-5EHWYJMC.js.map +0 -1
  108. package/dist/parser-E6U5TNAU.cjs.map +0 -1
  109. package/dist/parser-UGB3NIVH.js.map +0 -1
  110. package/dist/provider-5K7O3Z2O.cjs.map +0 -1
  111. package/dist/provider-7H4CPZYS.js.map +0 -1
  112. package/dist/provider-H4WWSPOV.js.map +0 -1
  113. package/dist/render-P6663O4I.js +0 -10
  114. package/dist/seal-7PPTXKKV.js +0 -10
  115. package/dist/setup-QSJ2INNS.js.map +0 -1
  116. package/dist/watch-RMX427YR.js.map +0 -1
  117. /package/dist/{-6HWEFXVV.js.map → -AS4IABL2.js.map} +0 -0
  118. /package/dist/{detect-NTR2FUEG.js.map → chunks-WVMGF7JL.js.map} +0 -0
  119. /package/dist/{page-range-CIRRJPXJ.js.map → detect-YEZX2XCF.js.map} +0 -0
  120. /package/dist/{page-range-OF5I4PQY.js.map → page-range-737B4EZW.js.map} +0 -0
  121. /package/dist/{profile-io-CAKRPQRD.js.map → page-range-LNMXJU6W.js.map} +0 -0
  122. /package/dist/{render-P6663O4I.js.map → profile-io-PLDQFPJA.js.map} +0 -0
  123. /package/dist/{seal-7PPTXKKV.js.map → redact-7ILEAUTS.js.map} +0 -0
package/dist/mcp.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  SIZE_KEYS,
15
15
  SIZE_PT_RANGE,
16
16
  buildGongmunOptions
17
- } from "./chunk-6GBLFQMA.js";
17
+ } from "./chunk-5RPYBC2Q.js";
18
18
  import {
19
19
  PRESET_ALIAS,
20
20
  compare,
@@ -31,34 +31,41 @@ import {
31
31
  patchHwp,
32
32
  patchHwpx,
33
33
  unknownFontWarnings
34
- } from "./chunk-TJJTLM76.js";
35
- import {
36
- blocksToMarkdown
37
- } from "./chunk-2SN3CKME.js";
34
+ } from "./chunk-L2XQQTOY.js";
35
+ import "./chunk-UOO7LSDG.js";
38
36
  import {
39
37
  detectFormat,
40
38
  detectZipFormat
41
- } from "./chunk-CFQLK3LM.js";
42
- import "./chunk-3TBUDJDE.js";
43
- import "./chunk-NGSHDBQR.js";
44
- import "./chunk-BKWHX3RC.js";
45
- import "./chunk-Q7EN4EUJ.js";
39
+ } from "./chunk-YOO6ET6M.js";
40
+ import "./chunk-MOL7MDBG.js";
41
+ import "./chunk-AX2R5Q2N.js";
42
+ import "./chunk-DZIXKL7E.js";
43
+ import "./chunk-F2ZU3IZG.js";
44
+ import {
45
+ blocksToMarkdown
46
+ } from "./chunk-SSTK6IKK.js";
47
+ import "./chunk-37HJHCPA.js";
48
+ import "./chunk-GQVSHGG4.js";
46
49
  import {
47
50
  KordocError,
48
51
  VERSION,
52
+ classifyError,
49
53
  sanitizeError,
50
54
  toArrayBuffer
51
- } from "./chunk-CNM75ZSX.js";
55
+ } from "./chunk-O5VS7RIR.js";
52
56
 
53
57
  // src/mcp.ts
54
58
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
55
59
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
56
60
  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"]);
61
+ import { readFileSync, writeFileSync, realpathSync, openSync, readSync, closeSync, statSync, mkdirSync, existsSync } from "fs";
62
+ import { pathToFileURL } from "url";
63
+ import { resolve, isAbsolute, extname, dirname, basename } from "path";
64
+ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".hwp", ".hwpx", ".hml", ".pdf", ".xls", ".xlsx", ".docx"]);
65
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".bmp"]);
66
+ var PROFILE_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
60
67
  var MAX_FILE_SIZE = 500 * 1024 * 1024;
61
- function safePath(filePath) {
68
+ function safePath(filePath, allowedExts = ALLOWED_EXTENSIONS) {
62
69
  if (!filePath) throw new KordocError("\uD30C\uC77C \uACBD\uB85C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4");
63
70
  const resolved = resolve(filePath);
64
71
  let real;
@@ -71,12 +78,53 @@ function safePath(filePath) {
71
78
  }
72
79
  if (!isAbsolute(real)) throw new KordocError("\uC808\uB300 \uACBD\uB85C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4");
73
80
  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(", ")})`);
81
+ 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
82
  return real;
76
83
  }
84
+ function safeOutputPath(outputPath, allowedExts) {
85
+ if (!outputPath) throw new KordocError("\uCD9C\uB825 \uACBD\uB85C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4");
86
+ const resolved = resolve(outputPath);
87
+ const ext = extname(resolved).toLowerCase();
88
+ if (!allowedExts.has(ext)) {
89
+ throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uCD9C\uB825 \uD655\uC7A5\uC790\uC785\uB2C8\uB2E4: ${ext || "(\uC5C6\uC74C)"} (\uD5C8\uC6A9: ${[...allowedExts].join(", ")})`);
90
+ }
91
+ const parent = dirname(resolved);
92
+ if (existsSync(parent)) {
93
+ try {
94
+ return resolve(realpathSync(parent), basename(resolved));
95
+ } catch (err) {
96
+ throw new KordocError(`\uCD9C\uB825 \uACBD\uB85C \uCC98\uB9AC \uC624\uB958 [${err?.code ?? "UNKNOWN"}]: ${parent}`);
97
+ }
98
+ }
99
+ return resolved;
100
+ }
101
+ function describeError(err) {
102
+ if (err instanceof KordocError) return err.message;
103
+ const code = err?.code;
104
+ if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
105
+ const hints = {
106
+ ENOENT: "\uD30C\uC77C \uB610\uB294 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
107
+ EACCES: "\uC811\uADFC \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
108
+ EPERM: "\uC791\uC5C5 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
109
+ EISDIR: "\uD30C\uC77C\uC774 \uC544\uB2C8\uB77C \uB514\uB809\uD1A0\uB9AC\uC785\uB2C8\uB2E4",
110
+ ENOTDIR: "\uACBD\uB85C \uC911\uAC04\uC774 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4",
111
+ ENOSPC: "\uB514\uC2A4\uD06C \uACF5\uAC04\uC774 \uBD80\uC871\uD569\uB2C8\uB2E4"
112
+ };
113
+ return `\uD30C\uC77C \uC2DC\uC2A4\uD15C \uC624\uB958 [${code}]: ${hints[code] ?? "\uACBD\uB85C\uC640 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694"}`;
114
+ }
115
+ const cls = classifyError(err);
116
+ return cls === "PARSE_ERROR" ? sanitizeError(err) : `\uBB38\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4 (${cls})`;
117
+ }
118
+ var MAX_RESPONSE_CHARS = 2e5;
119
+ function capResponseText(text, maxChars = MAX_RESPONSE_CHARS) {
120
+ if (text.length <= maxChars) return text;
121
+ return text.slice(0, maxChars) + `
122
+
123
+ \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]`;
124
+ }
77
125
  var MAX_METADATA_FILE_SIZE = 50 * 1024 * 1024;
78
- function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE) {
79
- const resolved = safePath(filePath);
126
+ function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE, allowedExts = ALLOWED_EXTENSIONS) {
127
+ const resolved = safePath(filePath, allowedExts);
80
128
  let fileSize;
81
129
  try {
82
130
  fileSize = statSync(resolved).size;
@@ -97,9 +145,9 @@ function readValidatedFile(filePath, maxSize = MAX_FILE_SIZE) {
97
145
  function detectFormatFromHeader(resolved) {
98
146
  const fd = openSync(resolved, "r");
99
147
  try {
100
- const headerBuf = Buffer.alloc(16);
101
- readSync(fd, headerBuf, 0, 16, 0);
102
- return detectFormat(toArrayBuffer(headerBuf));
148
+ const headerBuf = Buffer.alloc(512);
149
+ const bytesRead = readSync(fd, headerBuf, 0, 512, 0);
150
+ return detectFormat(toArrayBuffer(headerBuf.subarray(0, bytesRead)));
103
151
  } finally {
104
152
  closeSync(fd);
105
153
  }
@@ -116,7 +164,7 @@ server.tool(
116
164
  },
117
165
  async ({ file_path }) => {
118
166
  try {
119
- const { buffer } = readValidatedFile(file_path);
167
+ const { buffer, resolved } = readValidatedFile(file_path);
120
168
  const format = detectFormat(buffer);
121
169
  if (format === "unknown") {
122
170
  return {
@@ -124,7 +172,7 @@ server.tool(
124
172
  isError: true
125
173
  };
126
174
  }
127
- const result = await parse(buffer);
175
+ const result = await parse(buffer, { filePath: resolved });
128
176
  if (!result.success) {
129
177
  return {
130
178
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328 (${result.fileType}): ${result.error}` }],
@@ -156,11 +204,11 @@ ${warnText}`);
156
204
 
157
205
  ${markdown}`);
158
206
  return {
159
- content: [{ type: "text", text: parts.join("") }]
207
+ content: [{ type: "text", text: capResponseText(parts.join("")) }]
160
208
  };
161
209
  } catch (err) {
162
210
  return {
163
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
211
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
164
212
  isError: true
165
213
  };
166
214
  }
@@ -177,7 +225,7 @@ server.tool(
177
225
  const resolved = safePath(file_path);
178
226
  let format = detectFormatFromHeader(resolved);
179
227
  if (format === "hwpx") {
180
- const { buffer } = readValidatedFile(file_path, MAX_METADATA_FILE_SIZE);
228
+ const { buffer } = readValidatedFile(file_path);
181
229
  format = await detectZipFormat(buffer);
182
230
  }
183
231
  return {
@@ -185,7 +233,7 @@ server.tool(
185
233
  };
186
234
  } catch (err) {
187
235
  return {
188
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
236
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
189
237
  isError: true
190
238
  };
191
239
  }
@@ -211,7 +259,7 @@ server.tool(
211
259
  let metadata;
212
260
  let effectiveFormat = format;
213
261
  if (format === "hwpx") {
214
- const { detectZipFormat: detectZipFormat2 } = await import("./detect-NTR2FUEG.js");
262
+ const { detectZipFormat: detectZipFormat2 } = await import("./detect-YEZX2XCF.js");
215
263
  const zipFormat = await detectZipFormat2(buffer);
216
264
  if (zipFormat === "xlsx" || zipFormat === "docx") effectiveFormat = zipFormat;
217
265
  }
@@ -224,12 +272,14 @@ server.tool(
224
272
  break;
225
273
  case "pdf":
226
274
  try {
227
- const { extractPdfMetadataOnly } = await import("./parser-5EHWYJMC.js");
275
+ const { extractPdfMetadataOnly } = await import("./parser-PEOYBXBO.js");
228
276
  metadata = await extractPdfMetadataOnly(buffer);
229
277
  } catch {
230
278
  metadata = void 0;
231
279
  }
232
280
  break;
281
+ case "hwp3":
282
+ case "hwpml":
233
283
  case "xlsx":
234
284
  case "docx": {
235
285
  const result = await parse(buffer);
@@ -242,7 +292,7 @@ server.tool(
242
292
  };
243
293
  } catch (err) {
244
294
  return {
245
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
295
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
246
296
  isError: true
247
297
  };
248
298
  }
@@ -284,7 +334,7 @@ ${result.markdown}` }]
284
334
  };
285
335
  } catch (err) {
286
336
  return {
287
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
337
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
288
338
  isError: true
289
339
  };
290
340
  }
@@ -336,7 +386,7 @@ ${tableMarkdown}` }]
336
386
  };
337
387
  } catch (err) {
338
388
  return {
339
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
389
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
340
390
  isError: true
341
391
  };
342
392
  }
@@ -371,7 +421,7 @@ server.tool(
371
421
  };
372
422
  } catch (err) {
373
423
  return {
374
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
424
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
375
425
  isError: true
376
426
  };
377
427
  }
@@ -386,7 +436,7 @@ server.tool(
386
436
  async ({ file_path }) => {
387
437
  try {
388
438
  const { buffer } = readValidatedFile(file_path);
389
- const result = await parse(buffer);
439
+ const result = await parse(buffer, { keepTrailingEmptyCols: true });
390
440
  if (!result.success) {
391
441
  return {
392
442
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${result.error}` }],
@@ -399,7 +449,7 @@ server.tool(
399
449
  };
400
450
  } catch (err) {
401
451
  return {
402
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
452
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
403
453
  isError: true
404
454
  };
405
455
  }
@@ -427,6 +477,8 @@ server.tool(
427
477
  },
428
478
  async ({ file_path, fields, formats, require_unique, mask_values, output_format, output_path }) => {
429
479
  try {
480
+ const outExts = output_format === "markdown" ? /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]) : /* @__PURE__ */ new Set([".hwpx"]);
481
+ const outPath = output_path ? safeOutputPath(output_path, outExts) : void 0;
430
482
  const { buffer } = readValidatedFile(file_path);
431
483
  if (output_format === "hwpx-preserve") {
432
484
  const format = detectFormat(buffer);
@@ -458,16 +510,16 @@ server.tool(
458
510
  verifyLine
459
511
  ].filter(Boolean).join(" | ");
460
512
  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));
513
+ if (outPath) {
514
+ mkdirSync(dirname(outPath), { recursive: true });
515
+ writeFileSync(outPath, Buffer.from(hwpxResult.buffer));
464
516
  return {
465
517
  content: [{ type: "text", text: `[${summary2}]
466
518
 
467
519
  \uCC44\uC6CC\uC9C4 \uD544\uB4DC:
468
520
  ${filledList}
469
521
 
470
- HWPX \uD30C\uC77C \uC800\uC7A5 (\uC6D0\uBCF8 \uC11C\uC2DD \uC720\uC9C0): ${resolve(output_path)}` }]
522
+ HWPX \uD30C\uC77C \uC800\uC7A5 (\uC6D0\uBCF8 \uC11C\uC2DD \uC720\uC9C0): ${outPath}` }]
471
523
  };
472
524
  }
473
525
  return {
@@ -479,7 +531,7 @@ ${filledList}
479
531
  \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
532
  };
481
533
  }
482
- const result = await parse(buffer);
534
+ const result = await parse(buffer, { keepTrailingEmptyCols: true });
483
535
  if (!result.success) {
484
536
  return {
485
537
  content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${result.error}` }],
@@ -505,13 +557,13 @@ ${filledList}
505
557
  ].filter(Boolean).join(" | ");
506
558
  if (output_format === "hwpx") {
507
559
  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));
560
+ if (outPath) {
561
+ mkdirSync(dirname(outPath), { recursive: true });
562
+ writeFileSync(outPath, Buffer.from(hwpxBuffer));
511
563
  return {
512
564
  content: [{ type: "text", text: `[${summary}]
513
565
 
514
- HWPX \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}` }]
566
+ HWPX \uD30C\uC77C \uC800\uC7A5: ${outPath}` }]
515
567
  };
516
568
  }
517
569
  return {
@@ -522,13 +574,13 @@ HWPX \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}` }]
522
574
  ${previewMd}` }]
523
575
  };
524
576
  }
525
- if (output_path) {
526
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
527
- writeFileSync(resolve(output_path), markdown, "utf-8");
577
+ if (outPath) {
578
+ mkdirSync(dirname(outPath), { recursive: true });
579
+ writeFileSync(outPath, markdown, "utf-8");
528
580
  return {
529
581
  content: [{ type: "text", text: `[${summary}]
530
582
 
531
- \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uC800\uC7A5: ${resolve(output_path)}
583
+ \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uC800\uC7A5: ${outPath}
532
584
 
533
585
  ${previewMd}` }]
534
586
  };
@@ -540,7 +592,7 @@ ${previewMd}` }]
540
592
  };
541
593
  } catch (err) {
542
594
  return {
543
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
595
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
544
596
  isError: true
545
597
  };
546
598
  }
@@ -562,6 +614,7 @@ server.tool(
562
614
  },
563
615
  async ({ file_path, image_path, anchor, occurrence, size_mm, mode, dx_mm, dy_mm, output_path }) => {
564
616
  try {
617
+ const outPath = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx"]));
565
618
  const { buffer } = readValidatedFile(file_path);
566
619
  const format = detectFormat(buffer);
567
620
  if (format !== "hwpx") {
@@ -570,13 +623,13 @@ server.tool(
570
623
  isError: true
571
624
  };
572
625
  }
573
- const imgResolved = resolve(image_path);
626
+ const imgResolved = safePath(image_path, IMAGE_EXTENSIONS);
574
627
  if (statSync(imgResolved).size > 500 * 1024 * 1024) {
575
628
  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
629
  }
577
630
  const image = new Uint8Array(readFileSync(imgResolved));
578
- const ext = extname(image_path).slice(1).toLowerCase() || "png";
579
- const { placeSealHwpx } = await import("./seal-7PPTXKKV.js");
631
+ const ext = extname(imgResolved).slice(1).toLowerCase();
632
+ const { placeSealHwpx } = await import("./seal-7PTL6MXH.js");
580
633
  const result = await placeSealHwpx(buffer, [{
581
634
  anchor,
582
635
  occurrence,
@@ -587,8 +640,8 @@ server.tool(
587
640
  dxMm: dx_mm,
588
641
  dyMm: dy_mm
589
642
  }]);
590
- mkdirSync(dirname(resolve(output_path)), { recursive: true });
591
- writeFileSync(resolve(output_path), Buffer.from(result.buffer));
643
+ mkdirSync(dirname(outPath), { recursive: true });
644
+ writeFileSync(outPath, Buffer.from(result.buffer));
592
645
  const p0 = result.placed[0];
593
646
  const warnLines = (p0.warnings ?? []).map((w) => `
594
647
  \u26A0\uFE0F ${w}`).join("");
@@ -596,13 +649,13 @@ server.tool(
596
649
  content: [{
597
650
  type: "text",
598
651
  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}
652
+ \uC800\uC7A5: ${outPath}${warnLines}
600
653
  \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
654
  }]
602
655
  };
603
656
  } catch (err) {
604
657
  return {
605
- content: [{ type: "text", text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC2E4\uD328: ${sanitizeError(err)}` }],
658
+ content: [{ type: "text", text: `\uB3C4\uC7A5 \uBC30\uCE58 \uC2E4\uD328: ${describeError(err)}` }],
606
659
  isError: true
607
660
  };
608
661
  }
@@ -618,6 +671,7 @@ server.tool(
618
671
  },
619
672
  async ({ file_path, edited_markdown, output_path }) => {
620
673
  try {
674
+ const out = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx", ".hwp"]));
621
675
  const { buffer } = readValidatedFile(file_path);
622
676
  const format = detectFormat(buffer);
623
677
  let isHwpx = format === "hwpx";
@@ -639,7 +693,6 @@ server.tool(
639
693
  isError: true
640
694
  };
641
695
  }
642
- const out = resolve(output_path);
643
696
  mkdirSync(dirname(out), { recursive: true });
644
697
  writeFileSync(out, Buffer.from(result.data));
645
698
  const v = result.verification?.stats;
@@ -655,12 +708,147 @@ server.tool(
655
708
  };
656
709
  } catch (err) {
657
710
  return {
658
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
711
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
659
712
  isError: true
660
713
  };
661
714
  }
662
715
  }
663
716
  );
717
+ server.tool(
718
+ "render_document",
719
+ "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).",
720
+ {
721
+ file_path: z.string().min(1).describe("\uB80C\uB354\uD560 HWPX \uD30C\uC77C\uC758 \uC808\uB300 \uACBD\uB85C"),
722
+ 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"),
723
+ 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)"),
724
+ 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"),
725
+ reflow_mode: z.enum(["keep", "charAll"]).default("keep").describe("reflow \uC904\uBC14\uAFC8: keep=\uC5B4\uC808 \uB2E8\uC704, charAll=\uAE00\uC790 \uB2E8\uC704")
726
+ },
727
+ async ({ file_path, format, output_path, highlights, reflow_mode }) => {
728
+ try {
729
+ if (format === "svg" && !output_path) {
730
+ 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 };
731
+ }
732
+ const outPath = output_path ? safeOutputPath(output_path, /* @__PURE__ */ new Set([format === "png" ? ".png" : ".svg"])) : void 0;
733
+ const { buffer } = readValidatedFile(file_path, MAX_FILE_SIZE, /* @__PURE__ */ new Set([".hwpx"]));
734
+ const { renderHwpxToSvg } = await import("./render-T7S6H6AN.js");
735
+ const result = await renderHwpxToSvg(buffer, { highlights, reflow: true, reflowMode: reflow_mode });
736
+ const summary = [
737
+ `\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})`,
738
+ ...result.warnings.map((w) => `\u26A0\uFE0F ${w}`)
739
+ ];
740
+ if (format === "svg") {
741
+ mkdirSync(dirname(outPath), { recursive: true });
742
+ writeFileSync(outPath, result.svg, "utf-8");
743
+ summary.push(`\uC800\uC7A5: ${outPath}`);
744
+ return { content: [{ type: "text", text: summary.join("\n") }] };
745
+ }
746
+ const { rasterizeSvg } = await import("./rasterize-WWNQLKYW.js");
747
+ const raster = await rasterizeSvg(result.svg, result.width, result.height);
748
+ if (outPath) {
749
+ mkdirSync(dirname(outPath), { recursive: true });
750
+ writeFileSync(outPath, raster.png);
751
+ summary.push(`\uC800\uC7A5: ${outPath}`);
752
+ }
753
+ 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.`);
754
+ return {
755
+ content: [
756
+ { type: "image", data: raster.png.toString("base64"), mimeType: "image/png" },
757
+ { type: "text", text: summary.join("\n") }
758
+ ]
759
+ };
760
+ } catch (err) {
761
+ return {
762
+ content: [{ type: "text", text: `\uB80C\uB354 \uC2E4\uD328: ${describeError(err)}` }],
763
+ isError: true
764
+ };
765
+ }
766
+ }
767
+ );
768
+ server.tool(
769
+ "redact_document",
770
+ "\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.",
771
+ {
772
+ file_path: z.string().min(1).describe("\uB300\uC0C1 \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C"),
773
+ 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)"),
774
+ mask_char: z.string().min(1).max(1).optional().describe("\uB9C8\uC2A4\uD06C \uBB38\uC790 1\uAE00\uC790 (\uAE30\uBCF8: \u25CF)"),
775
+ 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"),
776
+ dry_run: z.boolean().default(false).describe("\uD0D0\uC9C0 \uB9AC\uD3EC\uD2B8\uB9CC \uBC18\uD658, \uD30C\uC77C \uBBF8\uC0DD\uC131")
777
+ },
778
+ async ({ file_path, rules, mask_char, output_path, dry_run }) => {
779
+ try {
780
+ const { buffer } = readValidatedFile(file_path);
781
+ const format = detectFormat(buffer);
782
+ const patchable = format === "hwpx" || format === "hwp";
783
+ if (!dry_run && !output_path) {
784
+ return { content: [{ type: "text", text: "output_path\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uD0D0\uC9C0\uB9CC \uC6D0\uD558\uBA74 dry_run: true)." }], isError: true };
785
+ }
786
+ const outPath = !dry_run ? safeOutputPath(output_path, new Set(patchable ? [format === "hwp" ? ".hwp" : ".hwpx"] : [".md", ".markdown", ".txt"])) : void 0;
787
+ const parsed = await parse(buffer, { filePath: file_path });
788
+ if (!parsed.success) {
789
+ return { content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}` }], isError: true };
790
+ }
791
+ const { redactMarkdown } = await import("./redact-7ILEAUTS.js");
792
+ const r = redactMarkdown(parsed.markdown, { rules, maskChar: mask_char });
793
+ const byRule = /* @__PURE__ */ new Map();
794
+ for (const h of r.hits) byRule.set(h.rule, (byRule.get(h.rule) ?? 0) + 1);
795
+ const lines = [
796
+ `\uD0D0\uC9C0: ${r.hits.length}\uAC74 (${[...byRule.entries()].map(([k, v]) => `${k} ${v}`).join(", ") || "\uC5C6\uC74C"})`,
797
+ ...r.hits.map((h) => ` - [${h.rule}] ${h.masked}`)
798
+ ];
799
+ if (!dry_run && r.hits.length > 0) {
800
+ if (patchable) {
801
+ const original = new Uint8Array(buffer);
802
+ const result = format === "hwp" ? await patchHwp(original, r.text) : await patchHwpx(original, r.text);
803
+ if (!result.success || !result.data) {
804
+ return { content: [{ type: "text", text: `\uB9C8\uC2A4\uD0B9 \uD328\uCE58 \uC2E4\uD328: ${result.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}` }], isError: true };
805
+ }
806
+ mkdirSync(dirname(outPath), { recursive: true });
807
+ writeFileSync(outPath, Buffer.from(result.data));
808
+ lines.push(`\uC800\uC7A5: ${outPath} (\uC6D0\uBCF8 \uC11C\uC2DD \uBCF4\uC874)`);
809
+ if (result.skipped.length > 0) {
810
+ 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:`);
811
+ for (const s of result.skipped) lines.push(` - ${s.reason}`);
812
+ }
813
+ lines.push("render_document\uB85C \uB9C8\uC2A4\uD0B9 \uACB0\uACFC\uB97C \uB208\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.");
814
+ } else {
815
+ mkdirSync(dirname(outPath), { recursive: true });
816
+ writeFileSync(outPath, r.text, "utf-8");
817
+ lines.push(`\uC800\uC7A5: ${outPath} (${format}\uB294 \uC11C\uC2DD \uBCF4\uC874 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4)`);
818
+ }
819
+ } else if (!dry_run) {
820
+ lines.push("\uD0D0\uC9C0 0\uAC74 \u2014 \uCD9C\uB825 \uD30C\uC77C\uC744 \uB9CC\uB4E4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
821
+ }
822
+ 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.");
823
+ return { content: [{ type: "text", text: capResponseText(lines.join("\n")) }] };
824
+ } catch (err) {
825
+ return { content: [{ type: "text", text: `\uB9C8\uC2A4\uD0B9 \uC2E4\uD328: ${describeError(err)}` }], isError: true };
826
+ }
827
+ }
828
+ );
829
+ server.tool(
830
+ "parse_chunks",
831
+ "\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.",
832
+ {
833
+ file_path: z.string().min(1).describe("\uB300\uC0C1 \uBB38\uC11C\uC758 \uC808\uB300 \uACBD\uB85C (HWP/HWPX/PDF/XLSX/DOCX)"),
834
+ 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"),
835
+ 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")
836
+ },
837
+ async ({ file_path, granularity, include_table_cells }) => {
838
+ try {
839
+ const { buffer } = readValidatedFile(file_path);
840
+ const parsed = await parse(buffer, { filePath: file_path });
841
+ if (!parsed.success) {
842
+ return { content: [{ type: "text", text: `\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}` }], isError: true };
843
+ }
844
+ const { blocksToChunks } = await import("./chunks-WVMGF7JL.js");
845
+ const chunks = blocksToChunks(parsed.blocks, { granularity, includeTableCells: include_table_cells });
846
+ return { content: [{ type: "text", text: capResponseText(JSON.stringify(chunks, null, 2)) }] };
847
+ } catch (err) {
848
+ return { content: [{ type: "text", text: `\uCCAD\uD06C \uD30C\uC2F1 \uC2E4\uD328: ${describeError(err)}` }], isError: true };
849
+ }
850
+ }
851
+ );
664
852
  server.tool(
665
853
  "extract_profile",
666
854
  '\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 +858,10 @@ server.tool(
670
858
  },
671
859
  async ({ hwpx_path, output_path }) => {
672
860
  try {
861
+ const out = safeOutputPath(output_path, PROFILE_EXTENSIONS);
673
862
  const { buffer } = readValidatedFile(hwpx_path);
674
- const { hwpxToProfile } = await import("./-6HWEFXVV.js");
863
+ const { hwpxToProfile } = await import("./-AS4IABL2.js");
675
864
  const profile = await hwpxToProfile(buffer);
676
- const out = resolve(output_path);
677
865
  mkdirSync(dirname(out), { recursive: true });
678
866
  writeFileSync(out, JSON.stringify(profile, null, 2));
679
867
  return {
@@ -682,7 +870,7 @@ server.tool(
682
870
  };
683
871
  } catch (err) {
684
872
  return {
685
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
873
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
686
874
  isError: true
687
875
  };
688
876
  }
@@ -755,11 +943,11 @@ server.tool(
755
943
  }
756
944
  let profile;
757
945
  if (profile_path) {
758
- const { parseFormatProfileJson } = await import("./profile-io-CAKRPQRD.js");
759
- profile = parseFormatProfileJson(readFileSync(resolve(profile_path), "utf-8"));
946
+ const { parseFormatProfileJson } = await import("./profile-io-PLDQFPJA.js");
947
+ profile = parseFormatProfileJson(readFileSync(safePath(profile_path, PROFILE_EXTENSIONS), "utf-8"));
760
948
  }
949
+ const out = safeOutputPath(output_path, /* @__PURE__ */ new Set([".hwpx"]));
761
950
  const buf = await markdownToHwpx(markdown, gongmun || profile ? { ...gongmun ? { gongmun } : {}, ...profile ? { profile } : {} } : void 0);
762
- const out = resolve(output_path);
763
951
  mkdirSync(dirname(out), { recursive: true });
764
952
  writeFileSync(out, Buffer.from(buf));
765
953
  const mode = gongmun ? `\uACF5\uBB38\uC11C:${gongmun.preset}` : "\uBC94\uC6A9";
@@ -775,18 +963,38 @@ server.tool(
775
963
  };
776
964
  } catch (err) {
777
965
  return {
778
- content: [{ type: "text", text: `\uC624\uB958: ${sanitizeError(err)}` }],
966
+ content: [{ type: "text", text: `\uC624\uB958: ${describeError(err)}` }],
779
967
  isError: true
780
968
  };
781
969
  }
782
970
  }
783
971
  );
784
- async function main() {
972
+ var serverStarted = false;
973
+ async function startMcpServer() {
974
+ if (serverStarted) return;
975
+ serverStarted = true;
785
976
  const transport = new StdioServerTransport();
786
977
  await server.connect(transport);
787
978
  }
788
- main().catch((err) => {
979
+ var autoStart = true;
980
+ try {
981
+ const entry = process.argv[1];
982
+ if (entry && pathToFileURL(realpathSync(entry)).href !== import.meta.url) autoStart = false;
983
+ } catch {
984
+ }
985
+ if (autoStart) startMcpServer().catch((err) => {
789
986
  console.error(err);
790
987
  process.exit(1);
791
988
  });
989
+ export {
990
+ ALLOWED_EXTENSIONS,
991
+ IMAGE_EXTENSIONS,
992
+ PROFILE_EXTENSIONS,
993
+ buildFillInputs,
994
+ capResponseText,
995
+ describeError,
996
+ safeOutputPath,
997
+ safePath,
998
+ startMcpServer
999
+ };
792
1000
  //# sourceMappingURL=mcp.js.map