@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
package/dist/errors.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Error model.
3
+ *
4
+ * A tool error is not a crash — it is a message to the model, and the model
5
+ * will try again based on what it reads. So every error carries a machine
6
+ * `code` plus, wherever we can produce one, a `hint` telling the model what to
7
+ * do differently. Error text quality directly determines retry success rate,
8
+ * which makes this file part of the product rather than plumbing.
9
+ */
10
+ /**
11
+ * An error that is safe and useful to hand back to a model.
12
+ *
13
+ * Anything thrown that is NOT an `McpToolError` is treated as unclassified and
14
+ * reported as `internal` — so a stack-leaking library error can never be
15
+ * mistaken for a designed, model-facing message.
16
+ */
17
+ export class McpToolError extends Error {
18
+ name = "McpToolError";
19
+ code;
20
+ hint;
21
+ constructor(code, message, options = {}) {
22
+ super(message, options);
23
+ this.code = code;
24
+ this.hint = options.hint;
25
+ }
26
+ }
27
+ /**
28
+ * Convenience constructors for the codes used most often.
29
+ *
30
+ * Each takes an optional `cause`, because documonster's own errors chain via
31
+ * `{ cause }` and the innermost message is usually the one naming the cell,
32
+ * OOXML part or ZIP entry that actually failed — dropping it would leave the
33
+ * model with a generic message it cannot act on.
34
+ */
35
+ export const toolError = {
36
+ invalidInput(message, hint, options) {
37
+ return new McpToolError("invalid_input", message, { hint, ...options });
38
+ },
39
+ notFound(message, hint, options) {
40
+ return new McpToolError("not_found", message, { hint, ...options });
41
+ },
42
+ outsideRoot(message, hint, options) {
43
+ return new McpToolError("outside_root", message, { hint, ...options });
44
+ },
45
+ readonly(message, hint, options) {
46
+ return new McpToolError("readonly", message, { hint, ...options });
47
+ },
48
+ tooLarge(message, hint, options) {
49
+ return new McpToolError("too_large", message, { hint, ...options });
50
+ },
51
+ unsupported(message, hint, options) {
52
+ return new McpToolError("unsupported", message, { hint, ...options });
53
+ }
54
+ };
55
+ /**
56
+ * Render any thrown value as the text body of an `isError` tool result.
57
+ *
58
+ * Includes the `cause` chain because documonster's own errors chain via
59
+ * `{ cause }`, and the innermost message is usually the one that says which
60
+ * cell, part or ZIP entry actually failed.
61
+ */
62
+ export function formatToolError(error) {
63
+ if (error instanceof McpToolError) {
64
+ const lines = [`[${error.code}] ${error.message}`];
65
+ const chain = describeCauseChain(error.cause);
66
+ if (chain !== undefined) {
67
+ lines.push(`Caused by: ${chain}`);
68
+ }
69
+ if (error.hint !== undefined) {
70
+ lines.push(`Hint: ${error.hint}`);
71
+ }
72
+ return lines.join("\n");
73
+ }
74
+ if (error instanceof Error) {
75
+ const lines = [`[internal] ${error.name}: ${error.message}`];
76
+ const chain = describeCauseChain(error.cause);
77
+ if (chain !== undefined) {
78
+ lines.push(`Caused by: ${chain}`);
79
+ }
80
+ return lines.join("\n");
81
+ }
82
+ return `[internal] ${String(error)}`;
83
+ }
84
+ /** Flatten `cause` links into `A: msg -> B: msg`, with a depth guard. */
85
+ function describeCauseChain(cause, depth = 0) {
86
+ if (cause === undefined || cause === null || depth >= 5) {
87
+ return undefined;
88
+ }
89
+ if (!(cause instanceof Error)) {
90
+ return String(cause);
91
+ }
92
+ const head = `${cause.name}: ${cause.message}`;
93
+ const tail = describeCauseChain(cause.cause, depth + 1);
94
+ return tail === undefined ? head : `${head} -> ${tail}`;
95
+ }
96
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@documonster/mcp` — programmatic entry point.
3
+ *
4
+ * Importing this package lets you embed the server in your own process (for
5
+ * example to attach a different transport, or to drive it from an agent
6
+ * framework) instead of spawning the `documonster-mcp` executable.
7
+ *
8
+ * ```ts
9
+ * import { createServer, resolveConfig } from "@documonster/mcp";
10
+ *
11
+ * const server = createServer(resolveConfig(["--root", "./workspace", "--readonly"]));
12
+ * await server.connect(myTransport);
13
+ * ```
14
+ */
15
+ export { ConfigError, readMetaFlags, resolveConfig, TOOL_GROUPS, usage } from "./config.js";
16
+ export type { ResolveConfigOptions, ServerConfig, ToolGroup } from "./config.js";
17
+ export { formatToolError, McpToolError, toolError } from "./errors.js";
18
+ export type { McpToolErrorOptions, ToolErrorCode } from "./errors.js";
19
+ export { assertWritable, isInside, resolveInRoot } from "./sandbox.js";
20
+ export type { ResolveOptions } from "./sandbox.js";
21
+ export { createServer } from "./server.js";
22
+ export type { ServerIdentity } from "./server.js";
23
+ export { ALL_TOOLS, selectTools } from "./tools/index.js";
24
+ export type { AnyToolDefinition, ToolContext, ToolDefinition } from "./tools/types.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@documonster/mcp` — programmatic entry point.
3
+ *
4
+ * Importing this package lets you embed the server in your own process (for
5
+ * example to attach a different transport, or to drive it from an agent
6
+ * framework) instead of spawning the `documonster-mcp` executable.
7
+ *
8
+ * ```ts
9
+ * import { createServer, resolveConfig } from "@documonster/mcp";
10
+ *
11
+ * const server = createServer(resolveConfig(["--root", "./workspace", "--readonly"]));
12
+ * await server.connect(myTransport);
13
+ * ```
14
+ */
15
+ export { ConfigError, readMetaFlags, resolveConfig, TOOL_GROUPS, usage } from "./config.js";
16
+ export { formatToolError, McpToolError, toolError } from "./errors.js";
17
+ export { assertWritable, isInside, resolveInRoot } from "./sandbox.js";
18
+ export { createServer } from "./server.js";
19
+ export { ALL_TOOLS, selectTools } from "./tools/index.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE5F,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Path sandbox.
3
+ *
4
+ * The single most important file in this package. An MCP server hands a model
5
+ * the ability to name files, so every path a tool touches must go through
6
+ * {@link resolveInRoot} — there is no other legitimate way to turn a
7
+ * model-supplied string into a filesystem path.
8
+ *
9
+ * Two attacks this defends against:
10
+ *
11
+ * 1. Traversal — `../../.ssh/id_rsa`, or a plain absolute path. Handled by
12
+ * resolving against the root and then checking containment.
13
+ * 2. Symlink escape — a link inside the root pointing outside it. Handled by
14
+ * realpath'ing the deepest part of the path that actually exists, which is
15
+ * why the check cannot be a naive `startsWith` on the raw string.
16
+ *
17
+ * Do NOT rely on the MCP client's tool-approval prompt for this. Programmatic
18
+ * clients have no prompt.
19
+ */
20
+ import type { ServerConfig } from "./config.js";
21
+ export interface ResolveOptions {
22
+ /**
23
+ * When true, the path must already exist. Use for inputs; leave false for
24
+ * write targets, whose parent is checked instead.
25
+ */
26
+ readonly mustExist?: boolean;
27
+ }
28
+ /**
29
+ * Turn a model-supplied path into an absolute path guaranteed to sit inside
30
+ * `config.root`.
31
+ *
32
+ * @throws {McpToolError} `invalid_input` for a URL or empty string,
33
+ * `not_found` when `mustExist` is set and it is not there, and
34
+ * `outside_root` for anything that escapes the sandbox.
35
+ */
36
+ export declare function resolveInRoot(config: ServerConfig, userPath: string, options?: ResolveOptions): Promise<string>;
37
+ /**
38
+ * Resolve a path a tool is about to write to, plus its sidecar files.
39
+ *
40
+ * A write target needs more than containment: every path the tool derives from
41
+ * it — a `.bak`, a temporary file — must be confined too, or a pre-planted
42
+ * symlink at `report.xlsx.bak` lets an in-place edit write outside the root.
43
+ */
44
+ export declare function resolveWriteTarget(config: ServerConfig, userPath: string, options?: {
45
+ readonly inPlace?: boolean;
46
+ }): Promise<WriteTarget>;
47
+ /** Resolve a write destination inside the disjoint output root. */
48
+ export declare function resolveOutputPath(config: ServerConfig, userPath: string): Promise<string>;
49
+ /** Public display spelling for a path under the output root. */
50
+ export declare function outputDisplay(userPath: string): string;
51
+ export interface WriteTarget {
52
+ /** Confined absolute path to write. */
53
+ readonly path: string;
54
+ /** Confined absolute path for the `.bak` sidecar. */
55
+ readonly backupPath: string;
56
+ /** The caller's spelling, for messages. */
57
+ readonly display: string;
58
+ }
59
+ /**
60
+ * Choose the destination for a tool that edits an existing file.
61
+ *
62
+ * Files already under `@output/` may be edited in place because that root is
63
+ * the server's private writable workspace. Input files require an explicit
64
+ * `out` unless the operator enabled the compatibility escape hatch.
65
+ */
66
+ export declare function resolveEditTarget(config: ServerConfig, sourcePath: string, out: string | undefined): Promise<WriteTarget>;
67
+ /**
68
+ * Assert the server is allowed to mutate the filesystem.
69
+ *
70
+ * Mutating tools are already withheld from `tools/list` under `--readonly`;
71
+ * this is the second line of defence for a client that calls a tool it was
72
+ * never told about.
73
+ *
74
+ * @remarks
75
+ * Called by every mutating tool (`sheet_write`, `archive_read` when extracting)
76
+ * before it touches the disk. `selectTools` filtering is the other half.
77
+ */
78
+ export declare function assertWritable(config: ServerConfig): void;
79
+ /**
80
+ * True when `child` is `parent` itself or lives beneath it.
81
+ *
82
+ * Compares path segments rather than raw strings, so `/root-2` is correctly
83
+ * rejected as a sibling of `/root` instead of matching a `startsWith("/root")`.
84
+ */
85
+ export declare function isInside(parent: string, child: string): boolean;
86
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAShD,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,OAAO,CAAC,WAAW,CAAC,CA2BtB;AAED,mEAAmE;AACnE,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB/F;AAyBD,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,WAAW,CAAC,CAQtB;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAOzD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAW/D"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Path sandbox.
3
+ *
4
+ * The single most important file in this package. An MCP server hands a model
5
+ * the ability to name files, so every path a tool touches must go through
6
+ * {@link resolveInRoot} — there is no other legitimate way to turn a
7
+ * model-supplied string into a filesystem path.
8
+ *
9
+ * Two attacks this defends against:
10
+ *
11
+ * 1. Traversal — `../../.ssh/id_rsa`, or a plain absolute path. Handled by
12
+ * resolving against the root and then checking containment.
13
+ * 2. Symlink escape — a link inside the root pointing outside it. Handled by
14
+ * realpath'ing the deepest part of the path that actually exists, which is
15
+ * why the check cannot be a naive `startsWith` on the raw string.
16
+ *
17
+ * Do NOT rely on the MCP client's tool-approval prompt for this. Programmatic
18
+ * clients have no prompt.
19
+ */
20
+ import fs from "node:fs/promises";
21
+ import path from "node:path";
22
+ import { toolError } from "./errors.js";
23
+ /** Symlink hops allowed before a path is treated as a loop. */
24
+ const MAX_SYMLINK_DEPTH = 32;
25
+ /** Matches a URL scheme (`http:`, `file:`) without matching a Windows drive letter (`C:`). */
26
+ const URL_SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]+:/;
27
+ /**
28
+ * Turn a model-supplied path into an absolute path guaranteed to sit inside
29
+ * `config.root`.
30
+ *
31
+ * @throws {McpToolError} `invalid_input` for a URL or empty string,
32
+ * `not_found` when `mustExist` is set and it is not there, and
33
+ * `outside_root` for anything that escapes the sandbox.
34
+ */
35
+ export async function resolveInRoot(config, userPath, options = {}) {
36
+ if (typeof userPath !== "string" || userPath.trim().length === 0) {
37
+ throw toolError.invalidInput("path must be a non-empty string");
38
+ }
39
+ if (URL_SCHEME_RE.test(userPath)) {
40
+ throw toolError.invalidInput(`path must be a local path inside the sandbox, not a URL: ${userPath}`, "Use a path relative to the server root. This server reads local files only.");
41
+ }
42
+ const outputRelative = outputPathPart(userPath);
43
+ const base = outputRelative === undefined ? config.root : config.outputRoot;
44
+ const relative = outputRelative ?? userPath;
45
+ const candidate = path.resolve(base, relative);
46
+ const resolved = await resolveSegments(candidate);
47
+ if (!isInside(base, resolved)) {
48
+ throw toolError.outsideRoot(`path resolves outside the server root: ${userPath}`, outputRelative === undefined
49
+ ? "Only paths inside the configured --root are readable. Ask the user to restart the server with a wider root if this file is genuinely needed."
50
+ : "Only paths inside --output-root are accessible through @output/.");
51
+ }
52
+ if (options.mustExist === true && !(await pathExists(resolved))) {
53
+ throw toolError.notFound(`no such file or directory: ${userPath}`, "Call doc_inspect on the parent directory to see what is actually there.");
54
+ }
55
+ return resolved;
56
+ }
57
+ /**
58
+ * Resolve a path a tool is about to write to, plus its sidecar files.
59
+ *
60
+ * A write target needs more than containment: every path the tool derives from
61
+ * it — a `.bak`, a temporary file — must be confined too, or a pre-planted
62
+ * symlink at `report.xlsx.bak` lets an in-place edit write outside the root.
63
+ */
64
+ export async function resolveWriteTarget(config, userPath, options = {}) {
65
+ if (options.inPlace === true) {
66
+ const outputRelative = outputPathPart(userPath);
67
+ if (outputRelative !== undefined) {
68
+ const target = await resolveOutputPath(config, outputRelative);
69
+ const backup = await resolveOutputPath(config, `${outputRelative}.bak`);
70
+ return {
71
+ path: target,
72
+ backupPath: backup,
73
+ display: `@output/${normaliseDisplay(outputRelative)}`
74
+ };
75
+ }
76
+ if (!config.allowInPlace) {
77
+ throw toolError.readonly("in-place edits are disabled", "Provide an `out` path to write safely under @output/, or restart the server with --allow-in-place.");
78
+ }
79
+ const target = await resolveInRoot(config, userPath);
80
+ const backup = await resolveInputSidecar(config, `${userPath}.bak`);
81
+ return { path: target, backupPath: backup, display: userPath };
82
+ }
83
+ const relative = outputPathPart(userPath) ?? userPath;
84
+ const target = await resolveOutputPath(config, relative);
85
+ const backup = await resolveOutputPath(config, `${relative}.bak`);
86
+ return { path: target, backupPath: backup, display: `@output/${normaliseDisplay(relative)}` };
87
+ }
88
+ /** Resolve a write destination inside the disjoint output root. */
89
+ export async function resolveOutputPath(config, userPath) {
90
+ const relative = outputPathPart(userPath) ?? userPath;
91
+ if (path.isAbsolute(relative)) {
92
+ throw toolError.invalidInput(`output path must be relative: ${userPath}`, "Write paths are relative to --output-root and are returned as @output/<path>.");
93
+ }
94
+ const candidate = path.resolve(config.outputRoot, relative);
95
+ const resolved = await resolveSegments(candidate);
96
+ if (!isInside(config.outputRoot, resolved)) {
97
+ throw toolError.outsideRoot(`output path resolves outside --output-root: ${userPath}`, "Use a relative output path without `..`.");
98
+ }
99
+ return resolved;
100
+ }
101
+ /** Resolve a sidecar beside an input file; available only in explicit in-place mode. */
102
+ async function resolveInputSidecar(config, userPath) {
103
+ const candidate = path.resolve(config.root, userPath);
104
+ const resolved = await resolveSegments(candidate);
105
+ if (!isInside(config.root, resolved)) {
106
+ throw toolError.outsideRoot(`sidecar path resolves outside --root: ${userPath}`);
107
+ }
108
+ return resolved;
109
+ }
110
+ /** Return the path after the virtual @output/ prefix, or undefined for input. */
111
+ function outputPathPart(userPath) {
112
+ const normalised = userPath.replace(/\\/g, "/");
113
+ if (normalised === "@output") {
114
+ return ".";
115
+ }
116
+ return normalised.startsWith("@output/") ? normalised.slice("@output/".length) : undefined;
117
+ }
118
+ function normaliseDisplay(relative) {
119
+ return relative.replace(/\\/g, "/").replace(/^\.\//, "");
120
+ }
121
+ /** Public display spelling for a path under the output root. */
122
+ export function outputDisplay(userPath) {
123
+ const relative = outputPathPart(userPath) ?? userPath;
124
+ return `@output/${normaliseDisplay(relative)}`;
125
+ }
126
+ /**
127
+ * Choose the destination for a tool that edits an existing file.
128
+ *
129
+ * Files already under `@output/` may be edited in place because that root is
130
+ * the server's private writable workspace. Input files require an explicit
131
+ * `out` unless the operator enabled the compatibility escape hatch.
132
+ */
133
+ export async function resolveEditTarget(config, sourcePath, out) {
134
+ if (out !== undefined) {
135
+ return await resolveWriteTarget(config, out);
136
+ }
137
+ if (outputPathPart(sourcePath) !== undefined) {
138
+ return await resolveWriteTarget(config, sourcePath, { inPlace: true });
139
+ }
140
+ return await resolveWriteTarget(config, sourcePath, { inPlace: true });
141
+ }
142
+ /** True when a path exists, following symlinks. */
143
+ async function pathExists(target) {
144
+ try {
145
+ await fs.stat(target);
146
+ return true;
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ }
152
+ /**
153
+ * Assert the server is allowed to mutate the filesystem.
154
+ *
155
+ * Mutating tools are already withheld from `tools/list` under `--readonly`;
156
+ * this is the second line of defence for a client that calls a tool it was
157
+ * never told about.
158
+ *
159
+ * @remarks
160
+ * Called by every mutating tool (`sheet_write`, `archive_read` when extracting)
161
+ * before it touches the disk. `selectTools` filtering is the other half.
162
+ */
163
+ export function assertWritable(config) {
164
+ if (config.readonly) {
165
+ throw toolError.readonly("this server is running in --readonly mode", "Report to the user that writing is disabled; do not retry.");
166
+ }
167
+ }
168
+ /**
169
+ * True when `child` is `parent` itself or lives beneath it.
170
+ *
171
+ * Compares path segments rather than raw strings, so `/root-2` is correctly
172
+ * rejected as a sibling of `/root` instead of matching a `startsWith("/root")`.
173
+ */
174
+ export function isInside(parent, child) {
175
+ if (child === parent) {
176
+ return true;
177
+ }
178
+ const relative = path.relative(parent, child);
179
+ return (relative.length > 0 &&
180
+ !relative.startsWith(`..${path.sep}`) &&
181
+ relative !== ".." &&
182
+ !path.isAbsolute(relative));
183
+ }
184
+ /**
185
+ * Realpath every segment of `target`, following symlinks explicitly.
186
+ *
187
+ * `fs.realpath` cannot be used for the whole path: it throws `ENOENT` both for
188
+ * "this does not exist" and for "this is a symlink whose target does not
189
+ * exist". Treating the second case as the first is a sandbox escape — a
190
+ * dangling link pointing outside the root would be reported as an ordinary
191
+ * not-yet-existing path inside it, and the tool would then create the file at
192
+ * the link's real destination.
193
+ *
194
+ * So each segment is resolved in turn: an existing symlink is read and its
195
+ * target resolved recursively, whether or not that target exists yet, while a
196
+ * genuinely missing segment is simply appended.
197
+ */
198
+ async function resolveSegments(target, depth = 0) {
199
+ if (depth > MAX_SYMLINK_DEPTH) {
200
+ throw toolError.invalidInput(`too many symbolic links while resolving ${target}`, "The path contains a symlink loop.");
201
+ }
202
+ const parent = path.dirname(target);
203
+ // `path.dirname` is a fixed point at the filesystem root, which ends recursion.
204
+ const resolvedParent = parent === target ? target : await resolveSegments(parent, depth + 1);
205
+ const candidate = path.join(resolvedParent, path.basename(target));
206
+ let stats;
207
+ try {
208
+ stats = await fs.lstat(candidate);
209
+ }
210
+ catch (cause) {
211
+ if (cause.code === "ENOENT") {
212
+ // Genuinely absent: a valid write target, and nothing left to follow.
213
+ return candidate;
214
+ }
215
+ throw cause;
216
+ }
217
+ if (!stats.isSymbolicLink()) {
218
+ return candidate;
219
+ }
220
+ const link = await fs.readlink(candidate);
221
+ const absolute = path.isAbsolute(link) ? link : path.join(resolvedParent, link);
222
+ return await resolveSegments(absolute, depth + 1);
223
+ }
224
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Server assembly.
3
+ *
4
+ * Deliberately transport-agnostic: this builds an `McpServer` with tools
5
+ * registered and hands it back. `cli.ts` attaches stdio; tests attach an
6
+ * in-memory transport. Keeping the two apart is what makes protocol-level
7
+ * tests possible without spawning a process, and leaves room to add a
8
+ * Streamable HTTP entry point later without touching tool code.
9
+ */
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import type { ServerConfig } from "./config.js";
12
+ /** Package identity reported during the MCP handshake. */
13
+ export interface ServerIdentity {
14
+ readonly name: string;
15
+ readonly version: string;
16
+ }
17
+ /**
18
+ * Build a configured MCP server. Nothing is connected yet — call
19
+ * `server.connect(transport)`.
20
+ */
21
+ export declare function createServer(config: ServerConfig, identity?: ServerIdentity): McpServer;
22
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,YAAY,EACpB,QAAQ,GAAE,cAAiC,GAC1C,SAAS,CAgCX"}
package/dist/server.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Server assembly.
3
+ *
4
+ * Deliberately transport-agnostic: this builds an `McpServer` with tools
5
+ * registered and hands it back. `cli.ts` attaches stdio; tests attach an
6
+ * in-memory transport. Keeping the two apart is what makes protocol-level
7
+ * tests possible without spawning a process, and leaves room to add a
8
+ * Streamable HTTP entry point later without touching tool code.
9
+ */
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { registerPrompts, registerResources } from "./capabilities.js";
12
+ import { formatToolError } from "./errors.js";
13
+ import { selectTools } from "./tools/index.js";
14
+ import { errorResult } from "./tools/result.js";
15
+ /**
16
+ * Fallback identity for programmatic callers that do not supply one.
17
+ *
18
+ * Deliberately not a plausible version number: the executable reads the real
19
+ * one from the package manifest, so anything reporting `0.0.0-unknown` in a
20
+ * client's logs is an embedder that should pass its own identity.
21
+ */
22
+ const DEFAULT_IDENTITY = {
23
+ name: "documonster",
24
+ version: "0.0.0-unknown"
25
+ };
26
+ /**
27
+ * Build a configured MCP server. Nothing is connected yet — call
28
+ * `server.connect(transport)`.
29
+ */
30
+ export function createServer(config, identity = DEFAULT_IDENTITY) {
31
+ const server = new McpServer({ name: identity.name, version: identity.version }, {
32
+ instructions: "Document toolkit for Excel, Word, PDF, CSV and ZIP files. Plain paths read from the input root; writes go to a separate output root and are returned as @output/<path>, which later tools can read. Input files are never modified unless the operator explicitly enabled in-place writes. Call doc_inspect before reading any document, and read narrow ranges rather than whole files. Call documonster_help for detailed guidance.",
33
+ capabilities: { tools: {}, resources: {}, prompts: {} }
34
+ });
35
+ // Resources publish the help topics at stable URIs, and prompts encode the
36
+ // inspect-then-read-then-verify order that the tool descriptions can only hint
37
+ // at. Neither costs a slot in the model's tool list.
38
+ registerResources(server);
39
+ registerPrompts(server, config);
40
+ const context = { config };
41
+ for (const tool of selectTools(config)) {
42
+ server.registerTool(tool.name, {
43
+ title: tool.title,
44
+ description: tool.description,
45
+ inputSchema: tool.inputSchema,
46
+ ...(tool.annotations === undefined ? {} : { annotations: tool.annotations })
47
+ }, async (args) => runTool(tool, args, context));
48
+ }
49
+ return server;
50
+ }
51
+ /**
52
+ * Invoke a tool, converting any throw into an `isError` result.
53
+ *
54
+ * A thrown exception would surface as a protocol-level failure, which tells
55
+ * the model nothing it can act on. An `isError` result carries the code and
56
+ * hint, which is what lets it correct itself and retry.
57
+ */
58
+ async function runTool(tool, args, context) {
59
+ try {
60
+ return await tool.handler(args, context);
61
+ }
62
+ catch (error) {
63
+ return errorResult(formatToolError(error));
64
+ }
65
+ }
66
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAShD;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,eAAe;CACzB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAoB,EACpB,QAAQ,GAAmB,gBAAgB;IAE3C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,EAClD;QACE,YAAY,EACV,uaAAua;QACza,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;KACxD,CACF,CAAC;IAEF,2EAA2E;IAC3E,+EAA+E;IAC/E,qDAAqD;IACrD,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC,MAAM,OAAO,GAAgB,EAAE,MAAM,EAAE,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,IAAI,EACT;YACE,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;SAC7E,EACD,KAAK,EAAE,IAA6B,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CACtE,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,OAAO,CACpB,IAAuB,EACvB,IAA6B,EAC7B,OAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `archive_read` — list or extract a ZIP/TAR archive.
3
+ *
4
+ * The entry point to most real work: documents arrive as email attachments and
5
+ * export bundles, and a model cannot open a binary container by itself. It is
6
+ * also where the security surface is widest, so three guards apply beyond the
7
+ * generic path sandbox:
8
+ *
9
+ * 1. **Traversal, including through existing links.** Entries are written one at
10
+ * a time and every destination goes through `resolveInRoot`, so an entry name
11
+ * with `..` is rejected *and* so is an entry whose path passes through a
12
+ * symlink already sitting in the destination directory. Delegating to the
13
+ * library's `extractTo` was not enough: it validates entry names against the
14
+ * target directory but writes through a pre-existing `out/link -> /elsewhere`,
15
+ * which was verified to escape the sandbox.
16
+ * 2. **Decompression bombs** — the archive file's own size is checked before it
17
+ * is opened, and the declared uncompressed sizes before anything is written.
18
+ * 3. **Symlink and special entries** — never materialised. A `link ->
19
+ * /etc/passwd` entry would otherwise leave a live escape hatch inside the
20
+ * sandbox for a later tool to follow.
21
+ * 4. **Permissions** — extracted files get the process default rather than the
22
+ * archive's mode bits, so an untrusted archive cannot produce something
23
+ * executable or world-writable.
24
+ *
25
+ * Encryption is not reported when listing: `ArchiveEntryInfo` carries an
26
+ * `isEncrypted` flag at runtime but does not declare it, and this package reads
27
+ * only documented public surface. Encryption therefore surfaces as a failed
28
+ * extraction with a hint to pass `password`. (Worth declaring upstream.)
29
+ */
30
+ export declare const archiveReadTool: import("./types.js").AnyToolDefinition;
31
+ //# sourceMappingURL=archive-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../../src/tools/archive-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAwBH,eAAO,MAAM,eAAe,wCAoN1B,CAAC"}