@reneturcios/docxredline 0.0.0-stage → 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rene Turcios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,149 @@
1
- # Temporary Holding Version
1
+ # docxredline
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ docxredline works on Word `.docx` files the way an agent needs to:
4
+ read one as plain text you can reason over, edit one so Word itself
5
+ shows the changes as tracked redlines, and scrub one clean before it
6
+ leaves the building. Everything is local — documents never leave the
7
+ machine, and there is no server component beyond the stdio MCP face.
8
+
9
+ This is a labelled core subset: it covers the three document tools
10
+ (`read_docx`, `process_document_batch`, `finalize_document`) and nothing
11
+ else. It does not drive a live Word instance, render any UI, or handle
12
+ document comments (see the notes per tool below).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npx @reneturcios/docxredline read agreement.docx
18
+ ```
19
+
20
+ Or run the container image:
21
+
22
+ ```bash
23
+ docker run -i ghcr.io/cloudcorprecords/docxredline
24
+ ```
25
+
26
+ The container defaults to the MCP server (see below). Pass CLI
27
+ arguments to use it as a CLI instead. Note the CLI reads and writes
28
+ files by path, so give the container a volume mount for your documents.
29
+
30
+ ## CLI
31
+
32
+ ```bash
33
+ # Read a document (tracked changes shown as CriticMarkup)
34
+ docxredline read agreement.docx
35
+
36
+ # Read the final clean version instead
37
+ docxredline read agreement.docx --clean
38
+
39
+ # Only paragraphs mentioning payment terms; synthetic page 2
40
+ docxredline read agreement.docx --search "payment"
41
+ docxredline read agreement.docx --page 2
42
+
43
+ # Apply a batch of edits from a JSON file
44
+ docxredline batch agreement.docx changes.json --author "Review Bot"
45
+
46
+ # Pre-send scrub, locked read-only
47
+ docxredline finalize agreement.docx --protection read_only
48
+
49
+ docxredline --version
50
+ ```
51
+
52
+ Exit codes: `0` on success, `1` when a file cannot be read or no change
53
+ in a batch applies, `2` for usage errors. All commands accept `--json`.
54
+
55
+ A changes file is a JSON array of change objects:
56
+
57
+ ```json
58
+ [
59
+ { "type": "modify", "target_text": "thirty days", "new_text": "sixty days" },
60
+ { "type": "modify", "target_text": "old clause", "new_text": "new clause", "match_mode": "all" },
61
+ { "type": "accept", "target_text": "12" },
62
+ { "type": "reject", "target_text": "13" }
63
+ ]
64
+ ```
65
+
66
+ ## MCP server
67
+
68
+ One bin serves both faces. The `mcp` subcommand starts a stdio MCP
69
+ server:
70
+
71
+ ```bash
72
+ npx -y @reneturcios/docxredline mcp
73
+ ```
74
+
75
+ MCP client configuration:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "docxredline": {
81
+ "command": "npx",
82
+ "args": ["-y", "@reneturcios/docxredline", "mcp"]
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Tools
89
+
90
+ | Tool | Input | What it does |
91
+ | --- | --- | --- |
92
+ | `read_docx` | `path` (string, required); `clean_view` (boolean), `search_query` (string), `page` (number) optional | Returns the document as Markdown-like text, paragraph by paragraph. Existing tracked changes render as CriticMarkup — `{++inserted++}` and `{--deleted--}` — unless `clean_view` resolves them away. `search_query` keeps only paragraphs containing the text. |
93
+ | `process_document_batch` | `path` (string, required), `changes` (array, required); `output_path`, `author` optional | Applies a batch of changes and writes the result (default: `<name>.redlined.docx` beside the input). Returns how many changes applied and a per-change error list for the rest. The output is written only when at least one change applied. |
94
+ | `finalize_document` | `path` (string, required); `output_path`, `protection_mode` optional | Writes a distribution-ready copy (default: `<name>.scrubbed.docx`): author fields in `docProps/core.xml` emptied, `w:rsid*` attributes stripped from the body, and — with `protection_mode: "read_only"` — a `documentProtection` lock added. |
95
+
96
+ ### How edits and IDs work in this subset
97
+
98
+ - A `modify` change finds `target_text` inside a single paragraph's
99
+ text and rewrites the containing run: the old text is wrapped in a
100
+ `<w:del>` element and the new text in a `<w:ins>` element, both
101
+ stamped with the batch `author` and fresh `w:id`s, so Word displays
102
+ a genuine tracked change.
103
+ - Safety gate: a non-regex `modify` with the default `match_mode`
104
+ `"first"` whose target matches **more than one paragraph** is
105
+ refused with an ambiguity error rather than applied to a guessed
106
+ paragraph. Use a longer target, or `match_mode: "all"` to edit every
107
+ occurrence in every matching paragraph. `regex: true` treats
108
+ `target_text` as a regular expression.
109
+ - A target that spans multiple runs inside its paragraph cannot be
110
+ rewritten by this subset and is reported as a per-change failure.
111
+ - `accept` / `reject` take the tracked change's `w:id` string as
112
+ `target_text`. Accept keeps inserted text and drops deletions;
113
+ reject does the inverse. (The original tool's richer ID model for
114
+ comments and ranges is not reproduced here.)
115
+ - `reply` is not supported in this subset: it comes back as a
116
+ per-change failure, `"comments not supported in this subset"`, and
117
+ is never silently counted as applied.
118
+
119
+ ### Synthetic paging
120
+
121
+ `.docx` has no page concept, so `page` is a local contract: paragraphs
122
+ are grouped 40 to a page, numbered from 1. `read_docx` reports the
123
+ document's total paragraph count and synthetic page count with every
124
+ result so callers can page deliberately.
125
+
126
+ ### finalize notes
127
+
128
+ When the document has no `word/settings.xml` part and protection is
129
+ requested, docxredline creates a minimal one carrying only the
130
+ protection element. A complete package would also register that part
131
+ in `[Content_Types].xml` and the package relationships; this subset
132
+ records that as a known gap rather than rewriting those parts.
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ npm install
138
+ npm run build # tsc -> dist/
139
+ npm test # node test runner over test/*.test.ts, no network
140
+ npm run dev # run the CLI from source via tsx
141
+ ```
142
+
143
+ Tests generate their own minimal `.docx` fixtures in a temp directory
144
+ with the same stored-ZIP writer the tool ships, so the suite exercises
145
+ the real archive reader end to end.
146
+
147
+ ## License
148
+
149
+ MIT — see `LICENSE`.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * docxredline CLI.
4
+ *
5
+ * docxredline read <path.docx> [--clean] [--search <text>] [--page <n>] [--json]
6
+ * docxredline batch <path.docx> <changes.json> [--output <path>] [--author <name>] [--json]
7
+ * docxredline finalize <path.docx> [--output <path>] [--protection read_only] [--json]
8
+ * docxredline mcp
9
+ *
10
+ * `batch` reads its changes from a JSON file holding an array of change
11
+ * objects ({type, target_text, new_text, ...}).
12
+ *
13
+ * Exit codes: 0 success, 1 file/edit error, 2 usage error.
14
+ */
15
+ import { readFileSync } from "node:fs";
16
+ import { finalizeDocument, processDocumentBatch, readDocx, VERSION } from "./docx.js";
17
+ function usage() {
18
+ console.error(`Usage:
19
+ docxredline read <path.docx> [--clean] [--search <text>] [--page <n>] [--json]
20
+ docxredline batch <path.docx> <changes.json> [--output <path>] [--author <name>] [--json]
21
+ docxredline finalize <path.docx> [--output <path>] [--protection <mode>] [--json]
22
+ docxredline mcp
23
+ docxredline --version`);
24
+ process.exit(2);
25
+ }
26
+ function flagValue(args, flag) {
27
+ const i = args.indexOf(flag);
28
+ return i !== -1 && i + 1 < args.length ? args[i + 1] : undefined;
29
+ }
30
+ function positional(args, flagsWithValues) {
31
+ const out = [];
32
+ for (let i = 0; i < args.length; i++) {
33
+ if (flagsWithValues.includes(args[i]))
34
+ i++;
35
+ else if (!args[i].startsWith("--"))
36
+ out.push(args[i]);
37
+ }
38
+ return out;
39
+ }
40
+ async function main() {
41
+ const [cmd, ...rest] = process.argv.slice(2);
42
+ if (cmd === "--version" || cmd === "-v") {
43
+ console.log(VERSION);
44
+ return;
45
+ }
46
+ if (cmd === "mcp") {
47
+ await import("./mcp.js");
48
+ return;
49
+ }
50
+ if (!cmd)
51
+ usage();
52
+ const asJson = rest.includes("--json");
53
+ try {
54
+ if (cmd === "read") {
55
+ const [input] = positional(rest, ["--search", "--page"]);
56
+ if (!input)
57
+ usage();
58
+ const pageRaw = flagValue(rest, "--page");
59
+ const result = readDocx(input, {
60
+ cleanView: rest.includes("--clean"),
61
+ searchQuery: flagValue(rest, "--search"),
62
+ page: pageRaw !== undefined ? Number(pageRaw) : undefined,
63
+ });
64
+ if (asJson)
65
+ console.log(JSON.stringify(result, null, 2));
66
+ else
67
+ console.log(result.text);
68
+ }
69
+ else if (cmd === "batch") {
70
+ const [input, changesFile] = positional(rest, ["--output", "--author"]);
71
+ if (!input || !changesFile)
72
+ usage();
73
+ const changes = JSON.parse(readFileSync(changesFile, "utf8"));
74
+ if (!Array.isArray(changes)) {
75
+ console.error("error: changes file must hold a JSON array of changes");
76
+ process.exit(1);
77
+ }
78
+ const result = processDocumentBatch({
79
+ path: input,
80
+ outputPath: flagValue(rest, "--output"),
81
+ author: flagValue(rest, "--author"),
82
+ changes,
83
+ });
84
+ if (asJson)
85
+ console.log(JSON.stringify(result, null, 2));
86
+ else {
87
+ console.log(`applied ${result.applied}, failed ${result.failed.length}` +
88
+ (result.output_path ? ` -> ${result.output_path}` : ""));
89
+ for (const f of result.failed)
90
+ console.log(` change ${f.index}: ${f.error}`);
91
+ }
92
+ if (result.applied === 0 && result.failed.length > 0)
93
+ process.exit(1);
94
+ }
95
+ else if (cmd === "finalize") {
96
+ const [input] = positional(rest, ["--output", "--protection"]);
97
+ if (!input)
98
+ usage();
99
+ const result = finalizeDocument({
100
+ path: input,
101
+ outputPath: flagValue(rest, "--output"),
102
+ protectionMode: flagValue(rest, "--protection"),
103
+ });
104
+ if (asJson)
105
+ console.log(JSON.stringify(result, null, 2));
106
+ else
107
+ console.log(`scrubbed -> ${result.output_path} (metadata_stripped=${result.metadata_stripped}, ` +
108
+ `rsid_removed=${result.rsid_attributes_removed}, protection=${result.protection})`);
109
+ }
110
+ else {
111
+ usage();
112
+ }
113
+ }
114
+ catch (err) {
115
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ void main();
120
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/docx.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { BatchInput, BatchResult, FinalizeInput, FinalizeResult, ReadDocxOptions, ReadDocxResult, ZipEntry } from "./types.js";
2
+ export declare const VERSION = "0.1.0";
3
+ /** Paragraphs per synthetic page for read_docx's `page` argument. */
4
+ export declare const PARAGRAPHS_PER_PAGE = 40;
5
+ /** Standard CRC-32 (IEEE 802.3 polynomial) of a byte string. */
6
+ export declare function crc32(data: Uint8Array): number;
7
+ /** Build a ZIP archive of stored (uncompressed) entries. */
8
+ export declare function writeZip(entries: ZipEntry[]): Buffer;
9
+ /** Read a ZIP archive (stored or deflated entries) into ordered entries. */
10
+ export declare function readZip(buf: Buffer): ZipEntry[];
11
+ /** Decode XML entities (&amp; &lt; &#65; &#x41; ...) in a text node. */
12
+ export declare function decodeXml(text: string): string;
13
+ /** Escape a string for use as XML text content. */
14
+ export declare function encodeXml(text: string): string;
15
+ /** Paragraph XML spans of a document.xml string, in order. */
16
+ export declare function splitParagraphs(documentXml: string): string[];
17
+ export declare function readDocx(inputPath: string, options?: ReadDocxOptions): ReadDocxResult;
18
+ export declare function processDocumentBatch(input: BatchInput): BatchResult;
19
+ export declare function finalizeDocument(input: FinalizeInput): FinalizeResult;
package/dist/docx.js ADDED
@@ -0,0 +1,592 @@
1
+ /**
2
+ * docxredline core: read, redline, and finalize .docx files, locally.
3
+ *
4
+ * A .docx is a ZIP of XML parts. This module carries its own minimal ZIP
5
+ * reader (stored + deflated entries, inflated with node:zlib) and ZIP
6
+ * writer (stored entries with locally computed CRC32), then works on
7
+ * word/document.xml as text:
8
+ *
9
+ * - Reading splits the body into <w:p> paragraphs, concatenates the
10
+ * <w:t> text of each, and renders existing <w:ins> / <w:del> tracked
11
+ * changes as CriticMarkup ({++ ++} / {-- --}). The clean view resolves
12
+ * them instead: inserted text stays, deleted text goes.
13
+ * - A modify edit finds its target inside one paragraph's concatenated
14
+ * <w:t> text and rewrites the containing run, wrapping the old text in
15
+ * a <w:del> and the new text in a <w:ins> so Word shows a native
16
+ * tracked change. Safety gate, mirroring the original tool: a
17
+ * non-regex target with match_mode "first" that matches more than one
18
+ * paragraph is refused as ambiguous rather than guessed at.
19
+ * - Accept / reject address one tracked-change element by its w:id
20
+ * (carried in the change's target_text): accept unwraps <w:ins> and
21
+ * drops <w:del>; reject does the inverse.
22
+ * - finalize empties the author fields of docProps/core.xml, strips
23
+ * w:rsid* attributes from the body, and can add a readOnly
24
+ * documentProtection element to word/settings.xml.
25
+ *
26
+ * Paragraphs are paged synthetically at 40 per page for read_docx's
27
+ * `page` argument; .docx has no page concept of its own.
28
+ */
29
+ import { readFileSync, writeFileSync } from "node:fs";
30
+ import path from "node:path";
31
+ import { inflateRawSync } from "node:zlib";
32
+ export const VERSION = "0.1.0";
33
+ /** Paragraphs per synthetic page for read_docx's `page` argument. */
34
+ export const PARAGRAPHS_PER_PAGE = 40;
35
+ // ---------------------------------------------------------------------------
36
+ // CRC32 + ZIP (stored writer, stored/deflated reader)
37
+ // ---------------------------------------------------------------------------
38
+ const CRC_TABLE = (() => {
39
+ const table = [];
40
+ for (let n = 0; n < 256; n++) {
41
+ let c = n;
42
+ for (let k = 0; k < 8; k++) {
43
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
44
+ }
45
+ table.push(c >>> 0);
46
+ }
47
+ return table;
48
+ })();
49
+ /** Standard CRC-32 (IEEE 802.3 polynomial) of a byte string. */
50
+ export function crc32(data) {
51
+ let crc = 0xffffffff;
52
+ for (let i = 0; i < data.length; i++) {
53
+ crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
54
+ }
55
+ return (crc ^ 0xffffffff) >>> 0;
56
+ }
57
+ function u16le(n) {
58
+ return [n & 0xff, (n >>> 8) & 0xff];
59
+ }
60
+ function u32le(n) {
61
+ return [n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff];
62
+ }
63
+ // DOS date pinned to 2026-10-26 so written archives are deterministic.
64
+ const DOS_TIME = 0;
65
+ const DOS_DATE = ((2026 - 1980) << 9) | (10 << 5) | 26;
66
+ /** Build a ZIP archive of stored (uncompressed) entries. */
67
+ export function writeZip(entries) {
68
+ const encoder = new TextEncoder();
69
+ const chunks = [];
70
+ const central = [];
71
+ let offset = 0;
72
+ for (const entry of entries) {
73
+ const nameBytes = encoder.encode(entry.name);
74
+ const crc = crc32(entry.data);
75
+ const localHeader = [
76
+ ...u32le(0x04034b50), // local file header signature
77
+ ...u16le(20), // version needed
78
+ ...u16le(0), // flags
79
+ ...u16le(0), // method 0 = stored
80
+ ...u16le(DOS_TIME),
81
+ ...u16le(DOS_DATE),
82
+ ...u32le(crc),
83
+ ...u32le(entry.data.length), // compressed size
84
+ ...u32le(entry.data.length), // uncompressed size
85
+ ...u16le(nameBytes.length),
86
+ ...u16le(0), // extra length
87
+ ];
88
+ chunks.push(...localHeader, ...nameBytes, ...entry.data);
89
+ central.push(...u32le(0x02014b50), // central directory signature
90
+ ...u16le(20), // version made by
91
+ ...u16le(20), // version needed
92
+ ...u16le(0), // flags
93
+ ...u16le(0), // method: stored
94
+ ...u16le(DOS_TIME), ...u16le(DOS_DATE), ...u32le(crc), ...u32le(entry.data.length), ...u32le(entry.data.length), ...u16le(nameBytes.length), ...u16le(0), // extra length
95
+ ...u16le(0), // comment length
96
+ ...u16le(0), // disk number start
97
+ ...u16le(0), // internal attributes
98
+ ...u32le(0), // external attributes
99
+ ...u32le(offset), // local header offset
100
+ ...nameBytes);
101
+ offset += localHeader.length + nameBytes.length + entry.data.length;
102
+ }
103
+ const endRecord = [
104
+ ...u32le(0x06054b50), // end of central directory signature
105
+ ...u16le(0), // disk number
106
+ ...u16le(0), // disk with central directory
107
+ ...u16le(entries.length), // entries on this disk
108
+ ...u16le(entries.length), // total entries
109
+ ...u32le(central.length), // central directory size
110
+ ...u32le(offset), // central directory offset
111
+ ...u16le(0), // comment length
112
+ ];
113
+ return Buffer.from([...chunks, ...central, ...endRecord]);
114
+ }
115
+ /** Read a ZIP archive (stored or deflated entries) into ordered entries. */
116
+ export function readZip(buf) {
117
+ let eocd = -1;
118
+ const earliest = Math.max(0, buf.length - 22 - 0xffff);
119
+ for (let i = buf.length - 22; i >= earliest; i--) {
120
+ if (buf.readUInt32LE(i) === 0x06054b50) {
121
+ eocd = i;
122
+ break;
123
+ }
124
+ }
125
+ if (eocd === -1) {
126
+ throw new Error("not a ZIP archive (end of central directory not found)");
127
+ }
128
+ const count = buf.readUInt16LE(eocd + 10);
129
+ let pos = buf.readUInt32LE(eocd + 16);
130
+ const entries = [];
131
+ for (let n = 0; n < count; n++) {
132
+ if (pos + 46 > buf.length || buf.readUInt32LE(pos) !== 0x02014b50) {
133
+ throw new Error("corrupt ZIP archive (bad central directory entry)");
134
+ }
135
+ const method = buf.readUInt16LE(pos + 10);
136
+ const compSize = buf.readUInt32LE(pos + 20);
137
+ const nameLen = buf.readUInt16LE(pos + 28);
138
+ const extraLen = buf.readUInt16LE(pos + 30);
139
+ const commentLen = buf.readUInt16LE(pos + 32);
140
+ const localOffset = buf.readUInt32LE(pos + 42);
141
+ const name = buf.toString("utf8", pos + 46, pos + 46 + nameLen);
142
+ if (localOffset + 30 > buf.length || buf.readUInt32LE(localOffset) !== 0x04034b50) {
143
+ throw new Error(`corrupt ZIP archive (bad local header for ${name})`);
144
+ }
145
+ const lNameLen = buf.readUInt16LE(localOffset + 26);
146
+ const lExtraLen = buf.readUInt16LE(localOffset + 28);
147
+ const dataStart = localOffset + 30 + lNameLen + lExtraLen;
148
+ const raw = buf.subarray(dataStart, dataStart + compSize);
149
+ let data;
150
+ if (method === 0) {
151
+ data = Buffer.from(raw);
152
+ }
153
+ else if (method === 8) {
154
+ data = inflateRawSync(raw);
155
+ }
156
+ else {
157
+ throw new Error(`unsupported ZIP compression method ${method} for ${name}`);
158
+ }
159
+ entries.push({ name, data });
160
+ pos += 46 + nameLen + extraLen + commentLen;
161
+ }
162
+ return entries;
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // XML helpers
166
+ // ---------------------------------------------------------------------------
167
+ const XML_ENTITIES = {
168
+ amp: "&",
169
+ lt: "<",
170
+ gt: ">",
171
+ quot: '"',
172
+ apos: "'",
173
+ };
174
+ /** Decode XML entities (&amp; &lt; &#65; &#x41; ...) in a text node. */
175
+ export function decodeXml(text) {
176
+ return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
177
+ if (body.startsWith("#x") || body.startsWith("#X")) {
178
+ return String.fromCodePoint(parseInt(body.slice(2), 16));
179
+ }
180
+ if (body.startsWith("#")) {
181
+ return String.fromCodePoint(parseInt(body.slice(1), 10));
182
+ }
183
+ return XML_ENTITIES[body] ?? whole;
184
+ });
185
+ }
186
+ /** Escape a string for use as XML text content. */
187
+ export function encodeXml(text) {
188
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
189
+ }
190
+ /** Escape a string for use inside a double-quoted XML attribute. */
191
+ function encodeAttr(text) {
192
+ return encodeXml(text).replace(/"/g, "&quot;");
193
+ }
194
+ /** Paragraph XML spans of a document.xml string, in order. */
195
+ export function splitParagraphs(documentXml) {
196
+ const spans = [];
197
+ const re = /<w:p\b[^>]*\/>|<w:p\b[^>]*>[\s\S]*?<\/w:p>/g;
198
+ let m;
199
+ while ((m = re.exec(documentXml)) !== null)
200
+ spans.push(m[0]);
201
+ return spans;
202
+ }
203
+ /** Replace the i-th paragraph span of document.xml (paragraph count is stable). */
204
+ function replaceParagraph(documentXml, index, replacement) {
205
+ const re = /<w:p\b[^>]*\/>|<w:p\b[^>]*>[\s\S]*?<\/w:p>/g;
206
+ let i = 0;
207
+ return documentXml.replace(re, (span) => (i++ === index ? replacement : span));
208
+ }
209
+ /**
210
+ * The text segments of one paragraph, in order, each tagged by whether it
211
+ * sits inside a <w:ins>, inside a <w:del> (its text lives in <w:delText>),
212
+ * or in ordinary body text.
213
+ */
214
+ function paragraphSegments(paragraphXml) {
215
+ const segments = [];
216
+ const tokenRe = /<[^>]+>|[^<]+/g;
217
+ const context = [];
218
+ let capture = null;
219
+ let buffer = "";
220
+ const kind = () => context.includes("del") ? "del" : context.includes("ins") ? "ins" : "normal";
221
+ const flush = () => {
222
+ if (capture !== null && buffer.length > 0) {
223
+ segments.push({ kind: capture === "delText" ? "del" : kind(), text: decodeXml(buffer) });
224
+ }
225
+ buffer = "";
226
+ };
227
+ let tok;
228
+ while ((tok = tokenRe.exec(paragraphXml)) !== null) {
229
+ const t = tok[0];
230
+ if (t.startsWith("<")) {
231
+ if (/^<w:ins\b/.test(t) && !t.endsWith("/>"))
232
+ context.push("ins");
233
+ else if (/^<\/w:ins\s*>/.test(t)) {
234
+ const i = context.lastIndexOf("ins");
235
+ if (i !== -1)
236
+ context.splice(i, 1);
237
+ }
238
+ else if (/^<w:del\b/.test(t) && !t.endsWith("/>"))
239
+ context.push("del");
240
+ else if (/^<\/w:del\s*>/.test(t)) {
241
+ const i = context.lastIndexOf("del");
242
+ if (i !== -1)
243
+ context.splice(i, 1);
244
+ }
245
+ else if (/^<w:t\b/.test(t) && !t.endsWith("/>")) {
246
+ flush();
247
+ capture = "t";
248
+ }
249
+ else if (/^<w:delText\b/.test(t) && !t.endsWith("/>")) {
250
+ flush();
251
+ capture = "delText";
252
+ }
253
+ else if (/^<\/w:(t|delText)\s*>/.test(t)) {
254
+ flush();
255
+ capture = null;
256
+ }
257
+ }
258
+ else if (capture !== null) {
259
+ buffer += t;
260
+ }
261
+ }
262
+ flush();
263
+ return segments;
264
+ }
265
+ /** Concatenated <w:t> text of a paragraph (inserted text in, deleted text out). */
266
+ function paragraphPlainText(paragraphXml) {
267
+ return paragraphSegments(paragraphXml)
268
+ .filter((s) => s.kind !== "del")
269
+ .map((s) => s.text)
270
+ .join("");
271
+ }
272
+ /** Render one paragraph in markup (CriticMarkup) or clean view. */
273
+ function renderParagraph(paragraphXml, cleanView) {
274
+ const out = [];
275
+ for (const seg of paragraphSegments(paragraphXml)) {
276
+ if (seg.kind === "ins")
277
+ out.push(cleanView ? seg.text : `{++${seg.text}++}`);
278
+ else if (seg.kind === "del") {
279
+ if (!cleanView)
280
+ out.push(`{--${seg.text}--}`);
281
+ }
282
+ else
283
+ out.push(seg.text);
284
+ }
285
+ return out.join("");
286
+ }
287
+ function loadDocx(inputPath) {
288
+ const resolvedPath = path.resolve(inputPath);
289
+ const buf = readFileSync(resolvedPath); // ENOENT etc. propagate as the real error
290
+ const entries = readZip(buf);
291
+ const doc = entries.find((e) => e.name === "word/document.xml");
292
+ if (!doc) {
293
+ throw new Error("not a .docx file (word/document.xml missing)");
294
+ }
295
+ return { resolvedPath, entries, documentXml: doc.data.toString("utf8") };
296
+ }
297
+ function saveDocx(entries, documentXml, outputPath) {
298
+ const next = entries.map((e) => e.name === "word/document.xml" ? { name: e.name, data: Buffer.from(documentXml, "utf8") } : e);
299
+ writeFileSync(outputPath, writeZip(next));
300
+ }
301
+ function siblingOutput(inputPath, suffix) {
302
+ const dir = path.dirname(inputPath);
303
+ const base = path.basename(inputPath).replace(/\.docx$/i, "");
304
+ return path.join(dir, `${base}.${suffix}.docx`);
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // read_docx
308
+ // ---------------------------------------------------------------------------
309
+ export function readDocx(inputPath, options = {}) {
310
+ const { resolvedPath, documentXml } = loadDocx(inputPath);
311
+ const cleanView = options.cleanView ?? false;
312
+ let paragraphs = splitParagraphs(documentXml).map((p) => renderParagraph(p, cleanView));
313
+ const total = paragraphs.length;
314
+ if (options.searchQuery !== undefined) {
315
+ const q = options.searchQuery;
316
+ paragraphs = paragraphs.filter((p) => p.includes(q));
317
+ }
318
+ const pageCount = Math.max(1, Math.ceil(total / PARAGRAPHS_PER_PAGE));
319
+ const result = {
320
+ path: resolvedPath,
321
+ text: "",
322
+ paragraphs,
323
+ paragraph_count: paragraphs.length,
324
+ total_paragraphs: total,
325
+ page_count: pageCount,
326
+ };
327
+ if (options.page !== undefined) {
328
+ const start = (options.page - 1) * PARAGRAPHS_PER_PAGE;
329
+ result.paragraphs = paragraphs.slice(start, start + PARAGRAPHS_PER_PAGE);
330
+ result.paragraph_count = result.paragraphs.length;
331
+ result.page = options.page;
332
+ }
333
+ result.text = result.paragraphs.join("\n\n");
334
+ return result;
335
+ }
336
+ function textRunSpans(paragraphXml) {
337
+ const spans = [];
338
+ const re = /<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g;
339
+ let m;
340
+ while ((m = re.exec(paragraphXml)) !== null) {
341
+ const openTag = m[0].slice(0, m[0].indexOf(">") + 1);
342
+ spans.push({ index: m.index, element: m[0], openTag, text: decodeXml(m[1]) });
343
+ }
344
+ return spans;
345
+ }
346
+ function findMatches(plain, change) {
347
+ const target = change.target_text ?? "";
348
+ if (change.regex) {
349
+ const re = new RegExp(target, "g");
350
+ const out = [];
351
+ let m;
352
+ while ((m = re.exec(plain)) !== null) {
353
+ out.push({ start: m.index, end: m.index + m[0].length, matched: m[0] });
354
+ if (m[0].length === 0)
355
+ re.lastIndex++;
356
+ }
357
+ return out;
358
+ }
359
+ const out = [];
360
+ let from = 0;
361
+ for (;;) {
362
+ const i = plain.indexOf(target, from);
363
+ if (i === -1)
364
+ break;
365
+ out.push({ start: i, end: i + target.length, matched: target });
366
+ from = i + Math.max(1, target.length);
367
+ }
368
+ return out;
369
+ }
370
+ /**
371
+ * Rewrite one paragraph so the plain-text range [start, end) becomes a
372
+ * tracked change: the old text wrapped in <w:del>, the new text in a
373
+ * following <w:ins>. The range must lie inside a single <w:t> element.
374
+ */
375
+ function applyModifyToParagraph(paragraphXml, range, newText, author, id, date) {
376
+ const spans = textRunSpans(paragraphXml);
377
+ let plainStart = 0;
378
+ for (const span of spans) {
379
+ const spanEnd = plainStart + span.text.length;
380
+ if (range.start >= plainStart && range.end <= spanEnd) {
381
+ const localStart = range.start - plainStart;
382
+ const localEnd = range.end - plainStart;
383
+ const prefix = span.text.slice(0, localStart);
384
+ const old = span.text.slice(localStart, localEnd);
385
+ const suffix = span.text.slice(localEnd);
386
+ const replacement = span.openTag +
387
+ encodeXml(prefix) +
388
+ `</w:t></w:r>` +
389
+ `<w:del w:id="${id}" w:author="${encodeAttr(author)}" w:date="${date}">` +
390
+ `<w:r><w:delText xml:space="preserve">${encodeXml(old)}</w:delText></w:r></w:del>` +
391
+ `<w:ins w:id="${id + 1}" w:author="${encodeAttr(author)}" w:date="${date}">` +
392
+ `<w:r><w:t xml:space="preserve">${encodeXml(newText)}</w:t></w:r></w:ins>` +
393
+ `<w:r><w:t xml:space="preserve">${encodeXml(suffix)}</w:t>`;
394
+ return (paragraphXml.slice(0, span.index) +
395
+ replacement +
396
+ paragraphXml.slice(span.index + span.element.length));
397
+ }
398
+ plainStart = spanEnd;
399
+ }
400
+ throw new Error("target spans multiple runs; not supported in this subset");
401
+ }
402
+ function nextTrackedId(documentXml) {
403
+ let max = 0;
404
+ const re = /w:id="(\d+)"/g;
405
+ let m;
406
+ while ((m = re.exec(documentXml)) !== null)
407
+ max = Math.max(max, parseInt(m[1], 10));
408
+ return max + 1;
409
+ }
410
+ function attrValue(attrs, name) {
411
+ const m = new RegExp(`(?:^|\\s)${name}="([^"]*)"`).exec(attrs);
412
+ return m ? m[1] : null;
413
+ }
414
+ /**
415
+ * Accept or reject the tracked-change element carrying w:id === id.
416
+ * Accept: unwrap <w:ins> (content stays), drop <w:del> (content goes).
417
+ * Reject: drop <w:ins>, unwrap <w:del> restoring its text as <w:t>.
418
+ * Returns the new XML, or null when no element carries that id.
419
+ */
420
+ function applyAcceptReject(documentXml, id, accept) {
421
+ const insRe = /<w:ins\b([^>]*)>([\s\S]*?)<\/w:ins>/g;
422
+ let m;
423
+ while ((m = insRe.exec(documentXml)) !== null) {
424
+ if (attrValue(m[1], "w:id") === id) {
425
+ const replacement = accept ? m[2] : "";
426
+ return documentXml.slice(0, m.index) + replacement + documentXml.slice(m.index + m[0].length);
427
+ }
428
+ }
429
+ const delRe = /<w:del\b([^>]*)>([\s\S]*?)<\/w:del>/g;
430
+ while ((m = delRe.exec(documentXml)) !== null) {
431
+ if (attrValue(m[1], "w:id") === id) {
432
+ const restored = m[2]
433
+ .replace(/<w:delText\b([^>]*)>/g, "<w:t$1>")
434
+ .replace(/<\/w:delText\s*>/g, "</w:t>");
435
+ const replacement = accept ? "" : restored;
436
+ return documentXml.slice(0, m.index) + replacement + documentXml.slice(m.index + m[0].length);
437
+ }
438
+ }
439
+ return null;
440
+ }
441
+ export function processDocumentBatch(input) {
442
+ const loaded = loadDocx(input.path);
443
+ let documentXml = loaded.documentXml;
444
+ const author = input.author ?? "docxredline";
445
+ const failed = [];
446
+ let applied = 0;
447
+ let idCounter = nextTrackedId(documentXml);
448
+ input.changes.forEach((change, index) => {
449
+ try {
450
+ if (change.type === "modify") {
451
+ if (!change.target_text)
452
+ throw new Error("modify requires target_text");
453
+ const mode = change.match_mode ?? "first";
454
+ if (mode !== "first" && mode !== "all") {
455
+ throw new Error(`unknown match_mode: ${mode}`);
456
+ }
457
+ const paragraphs = splitParagraphs(documentXml);
458
+ const plains = paragraphs.map(paragraphPlainText);
459
+ const matchesPerPara = plains.map((p) => findMatches(p, change));
460
+ const matchingParas = matchesPerPara
461
+ .map((matches, i) => (matches.length > 0 ? i : -1))
462
+ .filter((i) => i !== -1);
463
+ if (matchingParas.length === 0) {
464
+ throw new Error(`target not found: ${JSON.stringify(change.target_text)}`);
465
+ }
466
+ // Safety gate: an unanchored "first" edit must never guess between
467
+ // paragraphs. (Regex targets and match_mode "all" are exempt: the
468
+ // caller has opted into the broader behaviour explicitly.)
469
+ if (!change.regex && mode === "first" && matchingParas.length > 1) {
470
+ throw new Error(`ambiguous target: ${JSON.stringify(change.target_text)} matches ` +
471
+ `${matchingParas.length} paragraphs; refusing a "first" edit ` +
472
+ `(use match_mode "all" or a longer target)`);
473
+ }
474
+ const date = new Date().toISOString();
475
+ const newText = change.new_text ?? "";
476
+ const targets = mode === "all"
477
+ ? matchingParas.flatMap((pi) => matchesPerPara[pi].map((range) => ({ pi, range })))
478
+ : [{ pi: matchingParas[0], range: matchesPerPara[matchingParas[0]][0] }];
479
+ // Apply in reverse document order so earlier plain-text offsets
480
+ // stay valid while the paragraph XML grows around later edits.
481
+ targets.sort((a, b) => b.pi - a.pi || b.range.start - a.range.start);
482
+ for (const { pi, range } of targets) {
483
+ const current = splitParagraphs(documentXml)[pi];
484
+ const updated = applyModifyToParagraph(current, range, newText, author, idCounter, date);
485
+ idCounter += 2;
486
+ documentXml = replaceParagraph(documentXml, pi, updated);
487
+ }
488
+ applied++;
489
+ }
490
+ else if (change.type === "accept" || change.type === "reject") {
491
+ if (!change.target_text) {
492
+ throw new Error(`${change.type} requires target_text (the w:id of the tracked change)`);
493
+ }
494
+ const updated = applyAcceptReject(documentXml, change.target_text, change.type === "accept");
495
+ if (updated === null) {
496
+ throw new Error(`no tracked change with w:id ${JSON.stringify(change.target_text)}`);
497
+ }
498
+ documentXml = updated;
499
+ applied++;
500
+ }
501
+ else if (change.type === "reply") {
502
+ throw new Error("comments not supported in this subset");
503
+ }
504
+ else {
505
+ throw new Error(`unknown change type: ${JSON.stringify(change.type)}`);
506
+ }
507
+ }
508
+ catch (err) {
509
+ failed.push({ index, error: err instanceof Error ? err.message : String(err) });
510
+ }
511
+ });
512
+ const result = { applied, failed };
513
+ if (applied >= 1) {
514
+ const outputPath = path.resolve(input.outputPath ?? siblingOutput(loaded.resolvedPath, "redlined"));
515
+ saveDocx(loaded.entries, documentXml, outputPath);
516
+ result.output_path = outputPath;
517
+ }
518
+ return result;
519
+ }
520
+ // ---------------------------------------------------------------------------
521
+ // finalize_document
522
+ // ---------------------------------------------------------------------------
523
+ export function finalizeDocument(input) {
524
+ const loaded = loadDocx(input.path);
525
+ const entries = loaded.entries.map((e) => ({ name: e.name, data: e.data }));
526
+ // Strip w:rsid* attributes (Word's internal revision-save IDs) from the body.
527
+ let documentXml = loaded.documentXml;
528
+ const rsidMatches = documentXml.match(/\s+w:rsid[A-Za-z]*="[^"]*"/g);
529
+ const rsidRemoved = rsidMatches ? rsidMatches.length : 0;
530
+ if (rsidRemoved > 0) {
531
+ documentXml = documentXml.replace(/\s+w:rsid[A-Za-z]*="[^"]*"/g, "");
532
+ }
533
+ const docEntry = entries.find((e) => e.name === "word/document.xml");
534
+ if (docEntry)
535
+ docEntry.data = Buffer.from(documentXml, "utf8");
536
+ // Empty the author fields of docProps/core.xml.
537
+ let metadataStripped = false;
538
+ const core = entries.find((e) => e.name === "docProps/core.xml");
539
+ if (core) {
540
+ const scrubbed = core.data
541
+ .toString("utf8")
542
+ .replace(/(<dc:creator\b[^>]*>)[\s\S]*?(<\/dc:creator>)/g, "$1$2")
543
+ .replace(/(<cp:lastModifiedBy\b[^>]*>)[\s\S]*?(<\/cp:lastModifiedBy>)/g, "$1$2");
544
+ core.data = Buffer.from(scrubbed, "utf8");
545
+ metadataStripped = true;
546
+ }
547
+ // Optional document protection.
548
+ let protection = null;
549
+ if (input.protectionMode !== undefined) {
550
+ if (input.protectionMode !== "read_only") {
551
+ throw new Error(`unsupported protection_mode: ${JSON.stringify(input.protectionMode)}`);
552
+ }
553
+ const element = `<w:documentProtection w:edit="readOnly" w:enforcement="1"/>`;
554
+ const settings = entries.find((e) => e.name === "word/settings.xml");
555
+ if (settings) {
556
+ let xml = settings.data.toString("utf8");
557
+ if (/<w:documentProtection\b[^>]*\/>/.test(xml)) {
558
+ xml = xml.replace(/<w:documentProtection\b[^>]*\/>/, element);
559
+ }
560
+ else if (/<w:documentProtection\b[^>]*>[\s\S]*?<\/w:documentProtection>/.test(xml)) {
561
+ xml = xml.replace(/<w:documentProtection\b[^>]*>[\s\S]*?<\/w:documentProtection>/, element);
562
+ }
563
+ else if (/<w:settings\b[^>]*>/.test(xml)) {
564
+ xml = xml.replace(/(<w:settings\b[^>]*>)/, `$1${element}`);
565
+ }
566
+ else {
567
+ throw new Error("word/settings.xml has no <w:settings> root element");
568
+ }
569
+ settings.data = Buffer.from(xml, "utf8");
570
+ }
571
+ else {
572
+ // Minimal settings part. Note: a full .docx also needs a
573
+ // [Content_Types].xml override and a relationship entry for this
574
+ // part; see BUILD-REPORT gaps.
575
+ const minimal = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n` +
576
+ `<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">` +
577
+ `${element}</w:settings>`;
578
+ entries.push({ name: "word/settings.xml", data: Buffer.from(minimal, "utf8") });
579
+ }
580
+ protection = "read_only";
581
+ }
582
+ const outputPath = path.resolve(input.outputPath ?? siblingOutput(loaded.resolvedPath, "scrubbed"));
583
+ writeFileSync(outputPath, writeZip(entries));
584
+ return {
585
+ path: loaded.resolvedPath,
586
+ output_path: outputPath,
587
+ metadata_stripped: metadataStripped,
588
+ rsid_attributes_removed: rsidRemoved,
589
+ protection,
590
+ };
591
+ }
592
+ //# sourceMappingURL=docx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docx.js","sourceRoot":"","sources":["../src/docx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./docx.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./docx.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/mcp.js ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * docxredline MCP server. Tools: read_docx, process_document_batch,
3
+ * finalize_document — a labelled core subset of the public spec.
4
+ *
5
+ * Why the low-level Server instead of McpServer: the high-level helper
6
+ * converts zod schemas to JSON Schema and injects two keys that are not
7
+ * in this tool's public spec ("$schema" and "additionalProperties"),
8
+ * which breaks exact tool-list/schema parity with the spec. This file
9
+ * therefore serves tools/list with the spec's exact input schemas,
10
+ * while zod still validates every tools/call argument set. Tool names,
11
+ * parameters, and parameter descriptions are taken from the public spec.
12
+ *
13
+ * All logic is local: the tools read and write .docx files on disk.
14
+ */
15
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
18
+ import { z } from "zod";
19
+ import { finalizeDocument, processDocumentBatch, readDocx, VERSION } from "./docx.js";
20
+ const ReadDocxArgs = z.object({
21
+ path: z.string().describe("Path to the .docx file."),
22
+ clean_view: z
23
+ .boolean()
24
+ .optional()
25
+ .describe("Read the final clean version (accept all tracked changes)."),
26
+ search_query: z
27
+ .string()
28
+ .optional()
29
+ .describe("Only return paragraphs containing this text."),
30
+ page: z
31
+ .number()
32
+ .optional()
33
+ .describe("Synthetic page number to return (paragraphs are grouped into pages)."),
34
+ });
35
+ const ChangeArgs = z.object({
36
+ type: z.string().describe("modify, accept, reject, or reply."),
37
+ target_text: z.string().optional(),
38
+ new_text: z.string().optional(),
39
+ comment: z.string().optional(),
40
+ match_mode: z.string().optional().describe("first (default) or all."),
41
+ regex: z.boolean().optional(),
42
+ });
43
+ const ProcessDocumentBatchArgs = z.object({
44
+ path: z.string(),
45
+ output_path: z
46
+ .string()
47
+ .optional()
48
+ .describe("Where to write the edited .docx. Defaults to <name>.redlined.docx beside the input."),
49
+ author: z.string().optional().describe("Author name recorded on the tracked changes."),
50
+ changes: z.array(ChangeArgs).describe("The list of changes to apply atomically."),
51
+ });
52
+ const FinalizeDocumentArgs = z.object({
53
+ path: z.string(),
54
+ output_path: z.string().optional(),
55
+ protection_mode: z.string().optional().describe("e.g. read_only to lock the document."),
56
+ });
57
+ const TOOLS = [
58
+ {
59
+ name: "read_docx",
60
+ description: "Read a .docx file as clean Markdown-like text (CriticMarkup view of existing tracked changes), optionally filtered.",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ path: { type: "string", description: "Path to the .docx file." },
65
+ clean_view: {
66
+ type: "boolean",
67
+ description: "Read the final clean version (accept all tracked changes).",
68
+ },
69
+ search_query: {
70
+ type: "string",
71
+ description: "Only return paragraphs containing this text.",
72
+ },
73
+ page: {
74
+ type: "number",
75
+ description: "Synthetic page number to return (paragraphs are grouped into pages).",
76
+ },
77
+ },
78
+ required: ["path"],
79
+ },
80
+ },
81
+ {
82
+ name: "process_document_batch",
83
+ description: "Apply a batch of edits to a .docx as native Word tracked changes (redlines), or accept/reject/reply to existing tracked changes and comments by ID.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ path: { type: "string" },
88
+ output_path: {
89
+ type: "string",
90
+ description: "Where to write the edited .docx. Defaults to <name>.redlined.docx beside the input.",
91
+ },
92
+ author: { type: "string", description: "Author name recorded on the tracked changes." },
93
+ changes: {
94
+ type: "array",
95
+ items: {
96
+ type: "object",
97
+ properties: {
98
+ type: { type: "string", description: "modify, accept, reject, or reply." },
99
+ target_text: { type: "string" },
100
+ new_text: { type: "string" },
101
+ comment: { type: "string" },
102
+ match_mode: { type: "string", description: "first (default) or all." },
103
+ regex: { type: "boolean" },
104
+ },
105
+ required: ["type"],
106
+ },
107
+ description: "The list of changes to apply atomically.",
108
+ },
109
+ },
110
+ required: ["path", "changes"],
111
+ },
112
+ },
113
+ {
114
+ name: "finalize_document",
115
+ description: "Pre-send scrub: strip author metadata and internal tracking IDs from a .docx, optionally lock it, and prepare it for distribution.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ path: { type: "string" },
120
+ output_path: { type: "string" },
121
+ protection_mode: { type: "string", description: "e.g. read_only to lock the document." },
122
+ },
123
+ required: ["path"],
124
+ },
125
+ },
126
+ ];
127
+ function textResult(value) {
128
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
129
+ }
130
+ function errorResult(err) {
131
+ return {
132
+ content: [
133
+ { type: "text", text: err instanceof Error ? err.message : String(err) },
134
+ ],
135
+ isError: true,
136
+ };
137
+ }
138
+ const server = new Server({ name: "docxredline", version: VERSION }, { capabilities: { tools: {} } });
139
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
140
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
141
+ const { name, arguments: args } = request.params;
142
+ try {
143
+ if (name === "read_docx") {
144
+ const parsed = ReadDocxArgs.parse(args ?? {});
145
+ return textResult(readDocx(parsed.path, {
146
+ cleanView: parsed.clean_view,
147
+ searchQuery: parsed.search_query,
148
+ page: parsed.page,
149
+ }));
150
+ }
151
+ if (name === "process_document_batch") {
152
+ const parsed = ProcessDocumentBatchArgs.parse(args ?? {});
153
+ return textResult(processDocumentBatch({
154
+ path: parsed.path,
155
+ outputPath: parsed.output_path,
156
+ author: parsed.author,
157
+ changes: parsed.changes,
158
+ }));
159
+ }
160
+ if (name === "finalize_document") {
161
+ const parsed = FinalizeDocumentArgs.parse(args ?? {});
162
+ return textResult(finalizeDocument({
163
+ path: parsed.path,
164
+ outputPath: parsed.output_path,
165
+ protectionMode: parsed.protection_mode,
166
+ }));
167
+ }
168
+ }
169
+ catch (err) {
170
+ return errorResult(err);
171
+ }
172
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
173
+ });
174
+ async function main() {
175
+ await server.connect(new StdioServerTransport());
176
+ }
177
+ void main();
178
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * docxredline types.
3
+ *
4
+ * Everything here is a local file contract: inputs name .docx files on
5
+ * disk, outputs name the file that was written plus what happened inside
6
+ * it. There is no network surface and no injectable dependency.
7
+ */
8
+ export interface ReadDocxOptions {
9
+ /** Resolve existing tracked changes away (accept-all view). */
10
+ cleanView?: boolean;
11
+ /** Only return paragraphs whose rendered text contains this string. */
12
+ searchQuery?: string;
13
+ /** Synthetic 1-based page to return; paragraphs are grouped 40 per page. */
14
+ page?: number;
15
+ }
16
+ export interface ReadDocxResult {
17
+ /** The resolved input path that was read. */
18
+ path: string;
19
+ /** The returned paragraphs joined with blank lines. */
20
+ text: string;
21
+ /** The returned paragraphs, in document order. */
22
+ paragraphs: string[];
23
+ /** How many paragraphs are in `paragraphs` (after filter / paging). */
24
+ paragraph_count: number;
25
+ /** How many paragraphs the document holds in total (before filter / paging). */
26
+ total_paragraphs: number;
27
+ /** Total synthetic pages at 40 paragraphs per page. */
28
+ page_count: number;
29
+ /** The page that was returned, when a page was requested. */
30
+ page?: number;
31
+ }
32
+ export interface DocChange {
33
+ /** modify, accept, reject, or reply. */
34
+ type: string;
35
+ /**
36
+ * For modify: the text (or regex, when `regex` is set) to find.
37
+ * For accept / reject: the `w:id` string of the tracked-change element.
38
+ */
39
+ target_text?: string;
40
+ /** Replacement text for modify. */
41
+ new_text?: string;
42
+ /** Comment text; carried for shape compatibility, unused in this subset. */
43
+ comment?: string;
44
+ /** "first" (default) or "all". */
45
+ match_mode?: string;
46
+ /** Treat target_text as a regular expression for modify. */
47
+ regex?: boolean;
48
+ }
49
+ export interface BatchInput {
50
+ path: string;
51
+ outputPath?: string;
52
+ author?: string;
53
+ changes: DocChange[];
54
+ }
55
+ export interface BatchFailure {
56
+ /** Index of the change in the submitted batch. */
57
+ index: number;
58
+ error: string;
59
+ }
60
+ export interface BatchResult {
61
+ /** How many changes were applied. */
62
+ applied: number;
63
+ /** One entry per change that could not be applied, with the reason. */
64
+ failed: BatchFailure[];
65
+ /** Where the edited document was written; present only when applied >= 1. */
66
+ output_path?: string;
67
+ }
68
+ export interface FinalizeInput {
69
+ path: string;
70
+ outputPath?: string;
71
+ /** e.g. "read_only" to lock the document. */
72
+ protectionMode?: string;
73
+ }
74
+ export interface FinalizeResult {
75
+ /** The resolved input path. */
76
+ path: string;
77
+ /** Where the scrubbed document was written. */
78
+ output_path: string;
79
+ /** True when docProps/core.xml was present and its author fields emptied. */
80
+ metadata_stripped: boolean;
81
+ /** How many w:rsid* attributes were removed from word/document.xml. */
82
+ rsid_attributes_removed: number;
83
+ /** The protection mode that was applied, or null when none was requested. */
84
+ protection: string | null;
85
+ }
86
+ /** One entry of a ZIP archive, in archive order. */
87
+ export interface ZipEntry {
88
+ name: string;
89
+ data: Buffer;
90
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * docxredline types.
3
+ *
4
+ * Everything here is a local file contract: inputs name .docx files on
5
+ * disk, outputs name the file that was written plus what happened inside
6
+ * it. There is no network surface and no injectable dependency.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json CHANGED
@@ -1,6 +1 @@
1
- {
2
- "name": "@reneturcios/docxredline",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {"bin":{"docxredline":"dist/cli.js"},"dependencies":{"@modelcontextprotocol/sdk":"^1","zod":"^4"},"description":"Read .docx files as Markdown-like text with a CriticMarkup view of tracked changes, apply edits as native Word redlines, and scrub documents before sending. One bin serves a small CLI and a stdio MCP server.","devDependencies":{"@types/node":"^20","tsx":"^4","typescript":"^5"},"engines":{"node":">=20"},"files":["dist","server.json"],"homepage":"https://github.com/CloudCorpRecords/docxredline#readme","license":"MIT","mcpName":"io.github.CloudCorpRecords/docxredline","name":"@reneturcios/docxredline","private":false,"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/CloudCorpRecords/docxredline.git"},"scripts":{"build":"tsc","dev":"tsx src/cli.ts","test":"tsx --test test/*.test.ts"},"type":"module","version":"0.1.0"}
package/server.json ADDED
@@ -0,0 +1 @@
1
+ {"$schema":"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json","description":"Read, redline, and finalize .docx files with native Word tracked changes.","name":"io.github.CloudCorpRecords/docxredline","packages":[{"identifier":"@reneturcios/docxredline","packageArguments":[{"type":"positional","value":"mcp"}],"registryBaseUrl":"https://registry.npmjs.org","registryType":"npm","runtimeHint":"npx","transport":{"type":"stdio"},"version":"0.1.0"},{"identifier":"ghcr.io/cloudcorprecords/docxredline:0.1.0","packageArguments":[{"type":"positional","value":"mcp"}],"registryType":"oci","runtimeArguments":[{"name":"--rm","type":"named"},{"name":"-i","type":"named"}],"runtimeHint":"docker","transport":{"type":"stdio"}}],"repository":{"source":"github","url":"https://github.com/CloudCorpRecords/docxredline"},"version":"0.1.0"}