@documonster/mcp 0.8.0-canary.sha.09791dab

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 (127) hide show
  1. package/LICENSE +205 -0
  2. package/README.md +253 -0
  3. package/dist/capabilities.d.ts +29 -0
  4. package/dist/capabilities.d.ts.map +1 -0
  5. package/dist/capabilities.js +188 -0
  6. package/dist/capabilities.js.map +1 -0
  7. package/dist/cli.d.ts +13 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +64 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +65 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +189 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +66 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +96 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +25 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +20 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/sandbox.d.ts +86 -0
  24. package/dist/sandbox.d.ts.map +1 -0
  25. package/dist/sandbox.js +224 -0
  26. package/dist/sandbox.js.map +1 -0
  27. package/dist/server.d.ts +22 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +66 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/tools/archive-read.d.ts +31 -0
  32. package/dist/tools/archive-read.d.ts.map +1 -0
  33. package/dist/tools/archive-read.js +315 -0
  34. package/dist/tools/archive-read.js.map +1 -0
  35. package/dist/tools/archive-write.d.ts +9 -0
  36. package/dist/tools/archive-write.d.ts.map +1 -0
  37. package/dist/tools/archive-write.js +219 -0
  38. package/dist/tools/archive-write.js.map +1 -0
  39. package/dist/tools/chart.d.ts +93 -0
  40. package/dist/tools/chart.d.ts.map +1 -0
  41. package/dist/tools/chart.js +263 -0
  42. package/dist/tools/chart.js.map +1 -0
  43. package/dist/tools/doc-convert.d.ts +13 -0
  44. package/dist/tools/doc-convert.d.ts.map +1 -0
  45. package/dist/tools/doc-convert.js +209 -0
  46. package/dist/tools/doc-convert.js.map +1 -0
  47. package/dist/tools/doc-paginate.d.ts +14 -0
  48. package/dist/tools/doc-paginate.d.ts.map +1 -0
  49. package/dist/tools/doc-paginate.js +153 -0
  50. package/dist/tools/doc-paginate.js.map +1 -0
  51. package/dist/tools/doc-read.d.ts +14 -0
  52. package/dist/tools/doc-read.d.ts.map +1 -0
  53. package/dist/tools/doc-read.js +175 -0
  54. package/dist/tools/doc-read.js.map +1 -0
  55. package/dist/tools/doc-review.d.ts +17 -0
  56. package/dist/tools/doc-review.d.ts.map +1 -0
  57. package/dist/tools/doc-review.js +249 -0
  58. package/dist/tools/doc-review.js.map +1 -0
  59. package/dist/tools/doc-search.d.ts +20 -0
  60. package/dist/tools/doc-search.d.ts.map +1 -0
  61. package/dist/tools/doc-search.js +268 -0
  62. package/dist/tools/doc-search.js.map +1 -0
  63. package/dist/tools/doc-write.d.ts +11 -0
  64. package/dist/tools/doc-write.d.ts.map +1 -0
  65. package/dist/tools/doc-write.js +81 -0
  66. package/dist/tools/doc-write.js.map +1 -0
  67. package/dist/tools/document.d.ts +74 -0
  68. package/dist/tools/document.d.ts.map +1 -0
  69. package/dist/tools/document.js +157 -0
  70. package/dist/tools/document.js.map +1 -0
  71. package/dist/tools/form-fill.d.ts +20 -0
  72. package/dist/tools/form-fill.d.ts.map +1 -0
  73. package/dist/tools/form-fill.js +270 -0
  74. package/dist/tools/form-fill.js.map +1 -0
  75. package/dist/tools/formula-evaluate.d.ts +14 -0
  76. package/dist/tools/formula-evaluate.d.ts.map +1 -0
  77. package/dist/tools/formula-evaluate.js +192 -0
  78. package/dist/tools/formula-evaluate.js.map +1 -0
  79. package/dist/tools/fs-helpers.d.ts +106 -0
  80. package/dist/tools/fs-helpers.d.ts.map +1 -0
  81. package/dist/tools/fs-helpers.js +257 -0
  82. package/dist/tools/fs-helpers.js.map +1 -0
  83. package/dist/tools/help.d.ts +227 -0
  84. package/dist/tools/help.d.ts.map +1 -0
  85. package/dist/tools/help.js +262 -0
  86. package/dist/tools/help.js.map +1 -0
  87. package/dist/tools/index.d.ts +33 -0
  88. package/dist/tools/index.d.ts.map +1 -0
  89. package/dist/tools/index.js +78 -0
  90. package/dist/tools/index.js.map +1 -0
  91. package/dist/tools/inspect.d.ts +14 -0
  92. package/dist/tools/inspect.d.ts.map +1 -0
  93. package/dist/tools/inspect.js +463 -0
  94. package/dist/tools/inspect.js.map +1 -0
  95. package/dist/tools/pdf-edit.d.ts +26 -0
  96. package/dist/tools/pdf-edit.d.ts.map +1 -0
  97. package/dist/tools/pdf-edit.js +390 -0
  98. package/dist/tools/pdf-edit.js.map +1 -0
  99. package/dist/tools/result.d.ts +25 -0
  100. package/dist/tools/result.d.ts.map +1 -0
  101. package/dist/tools/result.js +43 -0
  102. package/dist/tools/result.js.map +1 -0
  103. package/dist/tools/sheet-edit.d.ts +16 -0
  104. package/dist/tools/sheet-edit.d.ts.map +1 -0
  105. package/dist/tools/sheet-edit.js +325 -0
  106. package/dist/tools/sheet-edit.js.map +1 -0
  107. package/dist/tools/sheet-read.d.ts +10 -0
  108. package/dist/tools/sheet-read.d.ts.map +1 -0
  109. package/dist/tools/sheet-read.js +139 -0
  110. package/dist/tools/sheet-read.js.map +1 -0
  111. package/dist/tools/sheet-write.d.ts +15 -0
  112. package/dist/tools/sheet-write.d.ts.map +1 -0
  113. package/dist/tools/sheet-write.js +290 -0
  114. package/dist/tools/sheet-write.js.map +1 -0
  115. package/dist/tools/spreadsheet.d.ts +100 -0
  116. package/dist/tools/spreadsheet.d.ts.map +1 -0
  117. package/dist/tools/spreadsheet.js +210 -0
  118. package/dist/tools/spreadsheet.js.map +1 -0
  119. package/dist/tools/template.d.ts +18 -0
  120. package/dist/tools/template.d.ts.map +1 -0
  121. package/dist/tools/template.js +209 -0
  122. package/dist/tools/template.js.map +1 -0
  123. package/dist/tools/types.d.ts +79 -0
  124. package/dist/tools/types.d.ts.map +1 -0
  125. package/dist/tools/types.js +18 -0
  126. package/dist/tools/types.js.map +1 -0
  127. package/package.json +74 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * `documonster_help` — on-demand documentation.
3
+ *
4
+ * Exists to solve a budget problem. Every enabled tool's `inputSchema` is sent
5
+ * to the model on every request, so a schema that spelled out the full option
6
+ * surface of a spreadsheet write would cost thousands of tokens permanently.
7
+ * Instead, tool schemas stay lean and the detail lives here, fetched only when
8
+ * the model actually needs it.
9
+ */
10
+ export interface HelpTopic {
11
+ readonly summary: string;
12
+ readonly body: string;
13
+ }
14
+ /**
15
+ * Topic registry. Add a topic whenever a tool schema has to stay lean but the
16
+ * detail is genuinely needed to use it correctly.
17
+ */
18
+ export declare const HELP_TOPICS: {
19
+ readonly overview: {
20
+ readonly summary: "What this server can do and the order to do it in.";
21
+ readonly body: `# documonster MCP server
22
+
23
+ Reads, writes and converts Excel, Word, PDF, CSV and ZIP documents on the local
24
+ filesystem. All work happens server-side: you send structure and parameters,
25
+ never document bytes.
26
+
27
+ ## Working discipline
28
+
29
+ 1. **Always \`doc_inspect\` first.** It is cheap and tells you the file type,
30
+ size, sheet list or page count, and CSV dialect. Reading blind wastes
31
+ context and usually picks the wrong sheet or column.
32
+ 2. **Then read a narrow range.** Reading a whole large sheet will exhaust your
33
+ context. Ask for the range you need and paginate.
34
+ 3. **Reference data by path, not by value.** Where a tool accepts a source
35
+ file, pass the path — do not copy rows through your own output. The server
36
+ moves the data without spending your tokens on it.
37
+ 4. **Reuse returned \`@output/...\` paths.** Plain paths are read-only input.
38
+ Every write returns a path below the separate output root; pass that exact
39
+ path to the next read, edit, convert or archive call.
40
+
41
+ ## Constraints
42
+
43
+ - Plain paths resolve inside the read-only input root. \`@output/...\` paths
44
+ resolve inside the separate writable output root. Absolute paths and \`..\`
45
+ that escape either root are rejected.
46
+ - The server may be running with writes disabled. If so, mutating tools are
47
+ simply absent from your tool list.
48
+ - Results are truncated to a fixed character budget, and truncation is always
49
+ marked explicitly. If you see a truncation marker, narrow your request.`;
50
+ };
51
+ readonly sandbox: {
52
+ readonly summary: "How paths are resolved and why some are rejected.";
53
+ readonly body: `# Path rules
54
+
55
+ - Relative paths resolve against the server's root directory.
56
+ - Absolute paths are permitted only when they land inside that root.
57
+ - Symlinks are followed and then re-checked, so a link pointing outside the
58
+ root is rejected even though it sits inside it.
59
+ - URLs are rejected by path arguments. Fetching remote documents is a
60
+ separate, opt-in server capability.
61
+
62
+ If you get \`[outside_root]\`, the file is genuinely unreachable — do not retry
63
+ with a different spelling of the same path. Tell the user the root needs to be
64
+ widened.`;
65
+ };
66
+ readonly roadmap: {
67
+ readonly summary: "Every tool, grouped by what it does.";
68
+ readonly body: `# Tools
69
+
70
+ ## Orientation
71
+ - \`documonster_help\` — this documentation.
72
+ - \`doc_inspect\` — identify a file or list a directory. Always first.
73
+
74
+ ## Spreadsheets
75
+ - \`sheet_read\` — read a bounded window.
76
+ - \`sheet_write\` — create an .xlsx from a spec.
77
+ - \`sheet_edit\` — patch an existing .xlsx.
78
+ - \`formula_evaluate\` — evaluate a formula against supplied values.
79
+
80
+ ## Documents
81
+ - \`doc_read\` — read .docx / .pdf / .md / .txt.
82
+ - \`doc_write\` — create a .docx or .pdf from Markdown.
83
+ - \`doc_edit\` — find and replace text in a .docx.
84
+ - \`doc_search\` — find text, or find text by its formatting.
85
+ - \`doc_paginate\` — real page counts and per-heading page numbers.
86
+ - \`doc_review\` — compare two versions, or review tracked changes.
87
+ - \`doc_convert\` — convert between formats.
88
+ - \`pdf_edit\` — watermark, number, stamp, rotate, delete/keep pages, append.
89
+
90
+ ## Forms and templates
91
+ - \`template_inspect\` / \`template_fill\` — Word {{placeholder}} templates.
92
+ - \`form_fill\` — Word form fields and PDF AcroForms.
93
+
94
+ ## Archives
95
+ - \`archive_read\` — list or extract a .zip/.tar.
96
+ - \`archive_write\` — package files into a .zip/.tar.
97
+
98
+ ## Not available — say so plainly rather than improvising
99
+ - reading a password-protected document
100
+ - PDF → Word, or any conversion with a PDF as the source
101
+ - OCR: a scanned PDF page yields no text, and a scanned form has no fields
102
+ - legacy binary .doc / .xls
103
+ - pivot tables and image insertion (the library supports them; no tool does yet)
104
+
105
+ ## Choosing between similar tools
106
+ - A Word file with \`{{placeholders}}\` → \`template_fill\`, not \`doc_edit\`.
107
+ - A Word file with grey form fields → \`form_fill\`, not \`doc_edit\`.
108
+ - Changing ordinary prose → \`doc_edit\`.
109
+ - Producing a PDF → make a .docx or .xlsx first, then \`doc_convert\`.
110
+ \`pdf_edit\` changes an existing PDF; it does not create content.
111
+ - Two versions of a document → \`doc_review\`, not two \`doc_read\` calls.
112
+ - A chart → a \`charts\` entry in \`sheet_write\`, or an \`add_chart\` op in
113
+ \`sheet_edit\`. There is no separate chart tool.
114
+ - Test data → \`generate\` in \`sheet_write\`. Never emit thousands of rows yourself.`;
115
+ };
116
+ readonly formulas: {
117
+ readonly summary: "Formula syntax notes and one engine limitation worth knowing.";
118
+ readonly body: `# Formulas
119
+
120
+ Both \`sheet_write\` (the \`formulas\` map) and \`formula_evaluate\` accept a
121
+ formula with or without a leading \`=\`; it is normalized for you.
122
+
123
+ ## Supported
124
+
125
+ Around 450 functions, including \`XLOOKUP\`, \`XMATCH\`, \`SUMIFS\`, \`LET\`,
126
+ dynamic arrays (\`FILTER\`, \`SORT\`, \`UNIQUE\`, \`SEQUENCE\`, \`TEXTSPLIT\`),
127
+ statistical, financial (\`XIRR\`, \`PRICE\`, \`COUPNUM\`), engineering and
128
+ database functions. Dynamic arrays spill into neighbouring cells, and
129
+ \`formula_evaluate\` reports the spilled values.
130
+
131
+ Higher-order functions work: \`MAP\`, \`REDUCE\`, \`SCAN\`, \`BYROW\`, \`BYCOL\`,
132
+ \`MAKEARRAY\`.
133
+
134
+ ## Known limitation
135
+
136
+ \`LAMBDA\` must be **bound to a name** before it is called. Excel accepts the
137
+ immediately-invoked form, this engine does not:
138
+
139
+ - \`LAMBDA(a,b,a+b)(2,3)\` → \`#NAME?\`
140
+ - \`LET(f,LAMBDA(a,b,a+b),f(2,3))\` → \`5\` ✓
141
+ - \`REDUCE(0,{1,2,3},LAMBDA(acc,x,acc+x))\` → \`6\` ✓
142
+
143
+ ## Errors are answers
144
+
145
+ \`#DIV/0!\`, \`#N/A\`, \`#VALUE!\`, \`#NAME?\`, \`#SPILL!\` come back as results,
146
+ not tool failures — usually you asked precisely in order to find out. \`#NAME?\`
147
+ most often means a misspelled function or an unsupported one.
148
+
149
+ ## Cross-sheet references
150
+
151
+ In \`sheet_write\`, a formula may reference another sheet in the same spec:
152
+ \`SUM('2026-06'!D:D)\`. Quote a sheet name that contains spaces or digits-only.`;
153
+ };
154
+ readonly documents: {
155
+ readonly summary: "How the Word / PDF / template / form tools fit together.";
156
+ readonly body: `# Documents
157
+
158
+ ## Reading
159
+
160
+ \`doc_read\` handles .docx, .pdf, .md and .txt. Word comes back as **Markdown**, so
161
+ headings, lists and tables survive. Use \`outline: true\` on a long Word file to
162
+ get just the headings before deciding what to read.
163
+
164
+ PDFs are reported page by page with \`## Page N\` markers, so you can cite a page.
165
+ There is **no OCR**: a scanned page reports no extractable text. \`doc_inspect\`
166
+ tells you up front whether a PDF has any text at all.
167
+
168
+ ## Writing
169
+
170
+ \`doc_write\` takes **Markdown** and produces .docx or .pdf. Write the content as
171
+ Markdown — that is the input language.
172
+
173
+ ## Converting
174
+
175
+ \`doc_convert\` moves between formats. PDF is terminal: nothing converts *from* a
176
+ PDF, because a faithful PDF→Word conversion does not exist. Lossy conversions
177
+ state their loss in the result.
178
+
179
+ ## Which tool for which .docx
180
+
181
+ Call \`doc_inspect\` and it will tell you, but the rule is:
182
+
183
+ - \`{{placeholder}}\` tags → \`template_inspect\` then \`template_fill\`
184
+ - grey form fields → \`form_fill\`
185
+ - tracked changes → \`doc_review\`
186
+ - ordinary prose → \`doc_read\` and \`doc_edit\`
187
+
188
+ Filling a template beats writing a document from scratch whenever one exists:
189
+ the styling is the template author's and you cannot break it.`;
190
+ };
191
+ readonly editing: {
192
+ readonly summary: "How to change an existing file safely.";
193
+ readonly body: `# Editing existing files
194
+
195
+ \`sheet_edit\`, \`doc_edit\`, \`pdf_edit\`, \`form_fill\`, \`doc_review\` and
196
+ \`doc_paginate\` all change a file someone already has.
197
+
198
+ ## Always read before you edit
199
+
200
+ Call \`sheet_read\` or \`doc_search\` first. The common failure is not a broken
201
+ edit but a **correct edit in the wrong place** — the wrong column, an off-by-one
202
+ row, a phrase that also occurs in a footer. Nothing looks wrong afterwards, which
203
+ is what makes it dangerous.
204
+
205
+ ## Use dryRun on anything that matters
206
+
207
+ \`sheet_edit\`, \`doc_edit\` and \`pdf_edit\` accept \`dryRun: true\`: it reports
208
+ exactly what would change and writes nothing. Do that whenever the file is the
209
+ user's own work rather than something you produced in this session.
210
+
211
+ ## What the tools guarantee
212
+
213
+ - **Atomic.** \`sheet_edit\` applies every operation in memory first; if one
214
+ fails, the file on disk is untouched.
215
+ - **Backed up.** An in-place edit copies the original to \`<name>.bak\`.
216
+ - **Narrow.** Only what you name changes.
217
+ - **No silent no-ops.** Replacing a phrase that does not occur reports that,
218
+ rather than rewriting the file unchanged.
219
+
220
+ ## Prefer writing a new file
221
+
222
+ Most of these tools accept \`out\`. An edit that never touches the original
223
+ cannot go wrong.`;
224
+ };
225
+ };
226
+ export declare const helpTool: import("./types.js").AnyToolDefinition;
227
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/tools/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW;;iBAEpB,OAAO,EAAE,oDAAoD;iBAC7D,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;0EA4BgE;;;iBAItE,OAAO,EAAE,mDAAmD;iBAC5D,IAAI,EAAE;;;;;;;;;;;SAWD;;;iBAIL,OAAO,EAAE,sCAAsC;iBAC/C,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sFA8C4E;;;iBAIlF,OAAO,EAAE,+DAA+D;iBACxE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gFAkCsE;;;iBAI5E,OAAO,EAAE,0DAA0D;iBACnE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8DAiCoD;;;iBAI1D,OAAO,EAAE,wCAAwC;iBACjD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BO;;CAE6B,CAAC;AAe/C,eAAO,MAAM,QAAQ,wCA8BnB,CAAC"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * `documonster_help` — on-demand documentation.
3
+ *
4
+ * Exists to solve a budget problem. Every enabled tool's `inputSchema` is sent
5
+ * to the model on every request, so a schema that spelled out the full option
6
+ * surface of a spreadsheet write would cost thousands of tokens permanently.
7
+ * Instead, tool schemas stay lean and the detail lives here, fetched only when
8
+ * the model actually needs it.
9
+ */
10
+ import { z } from "zod";
11
+ import { textResult } from "./result.js";
12
+ import { defineTool } from "./types.js";
13
+ /**
14
+ * Topic registry. Add a topic whenever a tool schema has to stay lean but the
15
+ * detail is genuinely needed to use it correctly.
16
+ */
17
+ export const HELP_TOPICS = {
18
+ overview: {
19
+ summary: "What this server can do and the order to do it in.",
20
+ body: `# documonster MCP server
21
+
22
+ Reads, writes and converts Excel, Word, PDF, CSV and ZIP documents on the local
23
+ filesystem. All work happens server-side: you send structure and parameters,
24
+ never document bytes.
25
+
26
+ ## Working discipline
27
+
28
+ 1. **Always \`doc_inspect\` first.** It is cheap and tells you the file type,
29
+ size, sheet list or page count, and CSV dialect. Reading blind wastes
30
+ context and usually picks the wrong sheet or column.
31
+ 2. **Then read a narrow range.** Reading a whole large sheet will exhaust your
32
+ context. Ask for the range you need and paginate.
33
+ 3. **Reference data by path, not by value.** Where a tool accepts a source
34
+ file, pass the path — do not copy rows through your own output. The server
35
+ moves the data without spending your tokens on it.
36
+ 4. **Reuse returned \`@output/...\` paths.** Plain paths are read-only input.
37
+ Every write returns a path below the separate output root; pass that exact
38
+ path to the next read, edit, convert or archive call.
39
+
40
+ ## Constraints
41
+
42
+ - Plain paths resolve inside the read-only input root. \`@output/...\` paths
43
+ resolve inside the separate writable output root. Absolute paths and \`..\`
44
+ that escape either root are rejected.
45
+ - The server may be running with writes disabled. If so, mutating tools are
46
+ simply absent from your tool list.
47
+ - Results are truncated to a fixed character budget, and truncation is always
48
+ marked explicitly. If you see a truncation marker, narrow your request.`
49
+ },
50
+ sandbox: {
51
+ summary: "How paths are resolved and why some are rejected.",
52
+ body: `# Path rules
53
+
54
+ - Relative paths resolve against the server's root directory.
55
+ - Absolute paths are permitted only when they land inside that root.
56
+ - Symlinks are followed and then re-checked, so a link pointing outside the
57
+ root is rejected even though it sits inside it.
58
+ - URLs are rejected by path arguments. Fetching remote documents is a
59
+ separate, opt-in server capability.
60
+
61
+ If you get \`[outside_root]\`, the file is genuinely unreachable — do not retry
62
+ with a different spelling of the same path. Tell the user the root needs to be
63
+ widened.`
64
+ },
65
+ roadmap: {
66
+ summary: "Every tool, grouped by what it does.",
67
+ body: `# Tools
68
+
69
+ ## Orientation
70
+ - \`documonster_help\` — this documentation.
71
+ - \`doc_inspect\` — identify a file or list a directory. Always first.
72
+
73
+ ## Spreadsheets
74
+ - \`sheet_read\` — read a bounded window.
75
+ - \`sheet_write\` — create an .xlsx from a spec.
76
+ - \`sheet_edit\` — patch an existing .xlsx.
77
+ - \`formula_evaluate\` — evaluate a formula against supplied values.
78
+
79
+ ## Documents
80
+ - \`doc_read\` — read .docx / .pdf / .md / .txt.
81
+ - \`doc_write\` — create a .docx or .pdf from Markdown.
82
+ - \`doc_edit\` — find and replace text in a .docx.
83
+ - \`doc_search\` — find text, or find text by its formatting.
84
+ - \`doc_paginate\` — real page counts and per-heading page numbers.
85
+ - \`doc_review\` — compare two versions, or review tracked changes.
86
+ - \`doc_convert\` — convert between formats.
87
+ - \`pdf_edit\` — watermark, number, stamp, rotate, delete/keep pages, append.
88
+
89
+ ## Forms and templates
90
+ - \`template_inspect\` / \`template_fill\` — Word {{placeholder}} templates.
91
+ - \`form_fill\` — Word form fields and PDF AcroForms.
92
+
93
+ ## Archives
94
+ - \`archive_read\` — list or extract a .zip/.tar.
95
+ - \`archive_write\` — package files into a .zip/.tar.
96
+
97
+ ## Not available — say so plainly rather than improvising
98
+ - reading a password-protected document
99
+ - PDF → Word, or any conversion with a PDF as the source
100
+ - OCR: a scanned PDF page yields no text, and a scanned form has no fields
101
+ - legacy binary .doc / .xls
102
+ - pivot tables and image insertion (the library supports them; no tool does yet)
103
+
104
+ ## Choosing between similar tools
105
+ - A Word file with \`{{placeholders}}\` → \`template_fill\`, not \`doc_edit\`.
106
+ - A Word file with grey form fields → \`form_fill\`, not \`doc_edit\`.
107
+ - Changing ordinary prose → \`doc_edit\`.
108
+ - Producing a PDF → make a .docx or .xlsx first, then \`doc_convert\`.
109
+ \`pdf_edit\` changes an existing PDF; it does not create content.
110
+ - Two versions of a document → \`doc_review\`, not two \`doc_read\` calls.
111
+ - A chart → a \`charts\` entry in \`sheet_write\`, or an \`add_chart\` op in
112
+ \`sheet_edit\`. There is no separate chart tool.
113
+ - Test data → \`generate\` in \`sheet_write\`. Never emit thousands of rows yourself.`
114
+ },
115
+ formulas: {
116
+ summary: "Formula syntax notes and one engine limitation worth knowing.",
117
+ body: `# Formulas
118
+
119
+ Both \`sheet_write\` (the \`formulas\` map) and \`formula_evaluate\` accept a
120
+ formula with or without a leading \`=\`; it is normalized for you.
121
+
122
+ ## Supported
123
+
124
+ Around 450 functions, including \`XLOOKUP\`, \`XMATCH\`, \`SUMIFS\`, \`LET\`,
125
+ dynamic arrays (\`FILTER\`, \`SORT\`, \`UNIQUE\`, \`SEQUENCE\`, \`TEXTSPLIT\`),
126
+ statistical, financial (\`XIRR\`, \`PRICE\`, \`COUPNUM\`), engineering and
127
+ database functions. Dynamic arrays spill into neighbouring cells, and
128
+ \`formula_evaluate\` reports the spilled values.
129
+
130
+ Higher-order functions work: \`MAP\`, \`REDUCE\`, \`SCAN\`, \`BYROW\`, \`BYCOL\`,
131
+ \`MAKEARRAY\`.
132
+
133
+ ## Known limitation
134
+
135
+ \`LAMBDA\` must be **bound to a name** before it is called. Excel accepts the
136
+ immediately-invoked form, this engine does not:
137
+
138
+ - \`LAMBDA(a,b,a+b)(2,3)\` → \`#NAME?\`
139
+ - \`LET(f,LAMBDA(a,b,a+b),f(2,3))\` → \`5\` ✓
140
+ - \`REDUCE(0,{1,2,3},LAMBDA(acc,x,acc+x))\` → \`6\` ✓
141
+
142
+ ## Errors are answers
143
+
144
+ \`#DIV/0!\`, \`#N/A\`, \`#VALUE!\`, \`#NAME?\`, \`#SPILL!\` come back as results,
145
+ not tool failures — usually you asked precisely in order to find out. \`#NAME?\`
146
+ most often means a misspelled function or an unsupported one.
147
+
148
+ ## Cross-sheet references
149
+
150
+ In \`sheet_write\`, a formula may reference another sheet in the same spec:
151
+ \`SUM('2026-06'!D:D)\`. Quote a sheet name that contains spaces or digits-only.`
152
+ },
153
+ documents: {
154
+ summary: "How the Word / PDF / template / form tools fit together.",
155
+ body: `# Documents
156
+
157
+ ## Reading
158
+
159
+ \`doc_read\` handles .docx, .pdf, .md and .txt. Word comes back as **Markdown**, so
160
+ headings, lists and tables survive. Use \`outline: true\` on a long Word file to
161
+ get just the headings before deciding what to read.
162
+
163
+ PDFs are reported page by page with \`## Page N\` markers, so you can cite a page.
164
+ There is **no OCR**: a scanned page reports no extractable text. \`doc_inspect\`
165
+ tells you up front whether a PDF has any text at all.
166
+
167
+ ## Writing
168
+
169
+ \`doc_write\` takes **Markdown** and produces .docx or .pdf. Write the content as
170
+ Markdown — that is the input language.
171
+
172
+ ## Converting
173
+
174
+ \`doc_convert\` moves between formats. PDF is terminal: nothing converts *from* a
175
+ PDF, because a faithful PDF→Word conversion does not exist. Lossy conversions
176
+ state their loss in the result.
177
+
178
+ ## Which tool for which .docx
179
+
180
+ Call \`doc_inspect\` and it will tell you, but the rule is:
181
+
182
+ - \`{{placeholder}}\` tags → \`template_inspect\` then \`template_fill\`
183
+ - grey form fields → \`form_fill\`
184
+ - tracked changes → \`doc_review\`
185
+ - ordinary prose → \`doc_read\` and \`doc_edit\`
186
+
187
+ Filling a template beats writing a document from scratch whenever one exists:
188
+ the styling is the template author's and you cannot break it.`
189
+ },
190
+ editing: {
191
+ summary: "How to change an existing file safely.",
192
+ body: `# Editing existing files
193
+
194
+ \`sheet_edit\`, \`doc_edit\`, \`pdf_edit\`, \`form_fill\`, \`doc_review\` and
195
+ \`doc_paginate\` all change a file someone already has.
196
+
197
+ ## Always read before you edit
198
+
199
+ Call \`sheet_read\` or \`doc_search\` first. The common failure is not a broken
200
+ edit but a **correct edit in the wrong place** — the wrong column, an off-by-one
201
+ row, a phrase that also occurs in a footer. Nothing looks wrong afterwards, which
202
+ is what makes it dangerous.
203
+
204
+ ## Use dryRun on anything that matters
205
+
206
+ \`sheet_edit\`, \`doc_edit\` and \`pdf_edit\` accept \`dryRun: true\`: it reports
207
+ exactly what would change and writes nothing. Do that whenever the file is the
208
+ user's own work rather than something you produced in this session.
209
+
210
+ ## What the tools guarantee
211
+
212
+ - **Atomic.** \`sheet_edit\` applies every operation in memory first; if one
213
+ fails, the file on disk is untouched.
214
+ - **Backed up.** An in-place edit copies the original to \`<name>.bak\`.
215
+ - **Narrow.** Only what you name changes.
216
+ - **No silent no-ops.** Replacing a phrase that does not occur reports that,
217
+ rather than rewriting the file unchanged.
218
+
219
+ ## Prefer writing a new file
220
+
221
+ Most of these tools accept \`out\`. An edit that never touches the original
222
+ cannot go wrong.`
223
+ }
224
+ };
225
+ /** Alias kept short for use inside this module. */
226
+ const TOPICS = HELP_TOPICS;
227
+ /**
228
+ * `z.enum` needs a non-empty tuple. `Object.keys` erases the literal key types,
229
+ * so one cast is unavoidable; it is sound because `TOPICS` is a non-empty
230
+ * object literal, and typing it as `TopicName` (rather than `string`) means a
231
+ * renamed topic breaks the handler lookup at compile time.
232
+ */
233
+ const TOPIC_NAMES = Object.keys(TOPICS);
234
+ export const helpTool = defineTool({
235
+ name: "documonster_help",
236
+ group: "core",
237
+ title: "documonster help",
238
+ description: "Read documentation for this server: usage conventions, path rules, and detailed parameter guidance that is deliberately kept out of tool schemas. Call with no topic to list available topics.",
239
+ inputSchema: {
240
+ topic: z
241
+ .enum(TOPIC_NAMES)
242
+ .optional()
243
+ .describe("Topic to read. Omit to list all topics with one-line summaries.")
244
+ },
245
+ annotations: { readOnlyHint: true, openWorldHint: false },
246
+ mutates: false,
247
+ handler: async (args, context) => {
248
+ const { topic } = args;
249
+ if (topic === undefined) {
250
+ const lines = ["# Available topics", ""];
251
+ for (const [name, entry] of Object.entries(TOPICS)) {
252
+ lines.push(`- \`${name}\` — ${entry.summary}`);
253
+ }
254
+ lines.push("", 'Read one with `documonster_help({ topic: "overview" })`.');
255
+ return textResult(context.config, lines.join("\n"));
256
+ }
257
+ // `z.enum` already rejected unknown topics, so this lookup cannot miss.
258
+ const entry = TOPICS[topic];
259
+ return textResult(context.config, entry?.body ?? "");
260
+ }
261
+ });
262
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/tools/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAOxC;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE;QACR,OAAO,EAAE,oDAAoD;QAC7D,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;0EA4BgE;KACvE;IAED,OAAO,EAAE;QACP,OAAO,EAAE,mDAAmD;QAC5D,IAAI,EAAE;;;;;;;;;;;SAWD;KACN;IAED,OAAO,EAAE;QACP,OAAO,EAAE,sCAAsC;QAC/C,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sFA8C4E;KACnF;IAED,QAAQ,EAAE;QACR,OAAO,EAAE,+DAA+D;QACxE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gFAkCsE;KAC7E;IAED,SAAS,EAAE;QACT,OAAO,EAAE,0DAA0D;QACnE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8DAiCoD;KAC3D;IAED,OAAO,EAAE;QACP,OAAO,EAAE,wCAAwC;QACjD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BO;KACd;CAC2C,CAAC;AAE/C,mDAAmD;AACnD,MAAM,MAAM,GAAG,WAAW,CAAC;AAI3B;;;;;GAKG;AACH,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAgC,CAAC;AAEvE,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IACjC,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,kBAAkB;IACzB,WAAW,EACT,gMAAgM;IAClM,WAAW,EAAE;QACX,KAAK,EAAE,CAAC;aACL,IAAI,CAAC,WAAW,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,iEAAiE,CAAC;KAC/E;IACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;IACzD,OAAO,EAAE,KAAK;IACd,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAC/B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QAEvB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;YACzC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnD,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,0DAA0D,CAAC,CAAC;YAC3E,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACtD,CAAC;QAED,wEAAwE;QACxE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Tool registry.
3
+ *
4
+ * The single list of every tool the server knows about. Selection is pure data
5
+ * filtering, which keeps `--enable` / `--readonly` behaviour testable without
6
+ * starting a transport.
7
+ */
8
+ import type { ServerConfig } from "../config.js";
9
+ import type { AnyToolDefinition } from "./types.js";
10
+ /**
11
+ * All tools, in the order they are registered.
12
+ *
13
+ * Keep this list short. Every enabled tool's name, description and JSON Schema
14
+ * is sent to the model on every single request, and the probability of the
15
+ * model picking the wrong tool rises with the count. A capability that only
16
+ * varies by a parameter belongs as a parameter, not as another tool.
17
+ */
18
+ export declare const ALL_TOOLS: readonly AnyToolDefinition[];
19
+ /**
20
+ * The tools a given configuration exposes.
21
+ *
22
+ * Always-mutating tools are removed under `--readonly`. Conditionally-writing
23
+ * tools remain visible so their read-only mode (list fields, compare versions,
24
+ * compute pages) still works; their write branch enforces `assertWritable`.
25
+ *
26
+ * @param tools - Defaults to {@link ALL_TOOLS}. Overridable so the filter can
27
+ * be tested against fixtures covering groups that no shipped tool uses yet —
28
+ * otherwise the only reachable path would be "nothing was filtered", which
29
+ * passes however broken the predicate is.
30
+ */
31
+ export declare function selectTools(config: ServerConfig, tools?: readonly AnyToolDefinition[]): readonly AnyToolDefinition[];
32
+ export type { AnyToolDefinition, ToolContext, ToolDefinition } from "./types.js";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkBjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,SAAS,iBAAiB,EAoBjD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,EACpB,KAAK,GAAE,SAAS,iBAAiB,EAAc,GAC9C,SAAS,iBAAiB,EAAE,CAW9B;AAED,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Tool registry.
3
+ *
4
+ * The single list of every tool the server knows about. Selection is pure data
5
+ * filtering, which keeps `--enable` / `--readonly` behaviour testable without
6
+ * starting a transport.
7
+ */
8
+ import { archiveReadTool } from "./archive-read.js";
9
+ import { archiveWriteTool } from "./archive-write.js";
10
+ import { docConvertTool } from "./doc-convert.js";
11
+ import { docPaginateTool } from "./doc-paginate.js";
12
+ import { docReadTool } from "./doc-read.js";
13
+ import { docReviewTool } from "./doc-review.js";
14
+ import { docEditTool, docSearchTool } from "./doc-search.js";
15
+ import { docWriteTool } from "./doc-write.js";
16
+ import { formFillTool } from "./form-fill.js";
17
+ import { formulaEvaluateTool } from "./formula-evaluate.js";
18
+ import { helpTool } from "./help.js";
19
+ import { inspectTool } from "./inspect.js";
20
+ import { pdfEditTool } from "./pdf-edit.js";
21
+ import { sheetEditTool } from "./sheet-edit.js";
22
+ import { sheetReadTool } from "./sheet-read.js";
23
+ import { sheetWriteTool } from "./sheet-write.js";
24
+ import { templateFillTool, templateInspectTool } from "./template.js";
25
+ /**
26
+ * All tools, in the order they are registered.
27
+ *
28
+ * Keep this list short. Every enabled tool's name, description and JSON Schema
29
+ * is sent to the model on every single request, and the probability of the
30
+ * model picking the wrong tool rises with the count. A capability that only
31
+ * varies by a parameter belongs as a parameter, not as another tool.
32
+ */
33
+ export const ALL_TOOLS = [
34
+ helpTool,
35
+ inspectTool,
36
+ sheetReadTool,
37
+ sheetWriteTool,
38
+ sheetEditTool,
39
+ formulaEvaluateTool,
40
+ docReadTool,
41
+ docWriteTool,
42
+ docConvertTool,
43
+ docSearchTool,
44
+ docEditTool,
45
+ docPaginateTool,
46
+ docReviewTool,
47
+ pdfEditTool,
48
+ templateInspectTool,
49
+ templateFillTool,
50
+ formFillTool,
51
+ archiveReadTool,
52
+ archiveWriteTool
53
+ ];
54
+ /**
55
+ * The tools a given configuration exposes.
56
+ *
57
+ * Always-mutating tools are removed under `--readonly`. Conditionally-writing
58
+ * tools remain visible so their read-only mode (list fields, compare versions,
59
+ * compute pages) still works; their write branch enforces `assertWritable`.
60
+ *
61
+ * @param tools - Defaults to {@link ALL_TOOLS}. Overridable so the filter can
62
+ * be tested against fixtures covering groups that no shipped tool uses yet —
63
+ * otherwise the only reachable path would be "nothing was filtered", which
64
+ * passes however broken the predicate is.
65
+ */
66
+ export function selectTools(config, tools = ALL_TOOLS) {
67
+ return tools.filter(tool => {
68
+ const groups = Array.isArray(tool.group) ? tool.group : [tool.group];
69
+ if (!groups.some(group => config.groups.has(group))) {
70
+ return false;
71
+ }
72
+ if (tool.mutates && config.readonly) {
73
+ return false;
74
+ }
75
+ return true;
76
+ });
77
+ }
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGtE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAiC;IACrD,QAAQ;IACR,WAAW;IACX,aAAa;IACb,cAAc;IACd,aAAa;IACb,mBAAmB;IACnB,WAAW;IACX,YAAY;IACZ,cAAc;IACd,aAAa;IACb,WAAW;IACX,eAAe;IACf,aAAa;IACb,WAAW;IACX,mBAAmB;IACnB,gBAAgB;IAChB,YAAY;IACZ,eAAe;IACf,gBAAgB;CACjB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CACzB,MAAoB,EACpB,KAAK,GAAiC,SAAS;IAE/C,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACpD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `doc_inspect` — the cheap first call.
3
+ *
4
+ * Every other tool is expensive in context: reading a sheet or a document body
5
+ * spends thousands of tokens. This one spends a few dozen and answers the
6
+ * questions that stop the model guessing — what kind of file is this, how big,
7
+ * and for CSV, which delimiter and line ending is it actually using.
8
+ *
9
+ * The CSV dialect fields are why this tool imports from the core package: a
10
+ * model has no way to know that a "csv" is semicolon-separated with a BOM,
11
+ * and getting it wrong silently produces a one-column table.
12
+ */
13
+ export declare const inspectTool: import("./types.js").AnyToolDefinition;
14
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/tools/inspect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwCH,eAAO,MAAM,WAAW,wCAoCtB,CAAC"}