@tiwater/office-mcp 0.10.0 → 0.11.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/office/README.md +20 -0
- package/office/index.mjs +95 -0
- package/package.json +1 -1
package/office/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Shared stdio MCP server for Office document workflows.
|
|
|
14
14
|
- `office_render_pdf`
|
|
15
15
|
- `xlsx_inspect`
|
|
16
16
|
- `xlsx_export_json`
|
|
17
|
+
- `xlsx_apply`
|
|
17
18
|
- `xlsx_validate`
|
|
18
19
|
- `pptx_inspect`
|
|
19
20
|
- `pptx_export_json`
|
|
@@ -23,6 +24,15 @@ Shared stdio MCP server for Office document workflows.
|
|
|
23
24
|
Install `@tiwater/office-mcp` together with the runtime versions required by
|
|
24
25
|
the consumer, then run `tiwater-office-mcp` as a stdio MCP server.
|
|
25
26
|
|
|
27
|
+
Office MCP 0.11 requires these minimum published runtimes on `PATH`:
|
|
28
|
+
|
|
29
|
+
| Command | Package | Minimum version |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `tiwater-docx` | NuGet `tiwater.docx.cli` | 0.15.0 |
|
|
32
|
+
| `tiwater-xlsx` | NuGet `tiwater.xlsx.cli` | 0.2.55 |
|
|
33
|
+
| `tiwater-pptx` | NuGet `tiwater.pptx.cli` | 0.2.22 |
|
|
34
|
+
| `tiwater-convert` | NuGet `tiwater.convert.cli` | 0.9.22 |
|
|
35
|
+
|
|
26
36
|
The server invokes published `tiwater-docx`, `tiwater-xlsx`,
|
|
27
37
|
`tiwater-pptx`, and `tiwater-convert` commands from `PATH`. It does not require
|
|
28
38
|
a source checkout or fall back to local projects.
|
|
@@ -32,6 +42,16 @@ tool arguments and structured results before they cross the protocol boundary.
|
|
|
32
42
|
Large observations and exports are written to a caller-selected new JSON
|
|
33
43
|
artifact. MCP returns only the artifact path, hash, and byte count.
|
|
34
44
|
|
|
45
|
+
## Workbook editing
|
|
46
|
+
|
|
47
|
+
`xlsx_apply` executes one existing `tiwater.xlsx-edit/v1` artifact against a
|
|
48
|
+
current `.xlsx` workbook. The caller's deterministic builder owns all values,
|
|
49
|
+
coordinates, and operation selection. Office MCP binds the current input,
|
|
50
|
+
operations artifact, and created output by path and content hash and records the
|
|
51
|
+
complete runtime result in a new receipt artifact. It does not interpret
|
|
52
|
+
scenario knowledge or derive workbook edits. Callers independently inspect and
|
|
53
|
+
validate the resulting workbook before delivery.
|
|
54
|
+
|
|
35
55
|
## Template migration
|
|
36
56
|
|
|
37
57
|
Template migration separates business choice from document mechanics:
|
package/office/index.mjs
CHANGED
|
@@ -138,6 +138,39 @@ const artifact = z.object({
|
|
|
138
138
|
bytes: z.number().int().nonnegative(),
|
|
139
139
|
}).strict();
|
|
140
140
|
|
|
141
|
+
const xlsxAppliedOperation = z.object({
|
|
142
|
+
type: z.string().min(1),
|
|
143
|
+
applied: z.boolean(),
|
|
144
|
+
detail: z.string(),
|
|
145
|
+
sheet: z.string().nullable().optional(),
|
|
146
|
+
changedRange: z.string().nullable().optional(),
|
|
147
|
+
warnings: z.array(z.string()).nullable().optional(),
|
|
148
|
+
}).strict();
|
|
149
|
+
const xlsxEditResult = z.object({
|
|
150
|
+
input: z.string().min(1),
|
|
151
|
+
output: z.string().min(1),
|
|
152
|
+
appliedOperations: z.array(xlsxAppliedOperation),
|
|
153
|
+
}).strict();
|
|
154
|
+
const xlsxApplyReceipt = z.object({
|
|
155
|
+
schema: z.literal('tiwater.office.xlsx-apply-receipt/v1'),
|
|
156
|
+
pass: z.boolean(),
|
|
157
|
+
input: artifact,
|
|
158
|
+
operations: artifact,
|
|
159
|
+
output: artifact.nullable(),
|
|
160
|
+
appliedOperations: z.array(xlsxAppliedOperation),
|
|
161
|
+
}).strict();
|
|
162
|
+
const xlsxApplyOutput = z.object({
|
|
163
|
+
tool: z.literal('xlsx_apply'),
|
|
164
|
+
runtime: runtimeIdentity,
|
|
165
|
+
receipt: artifact,
|
|
166
|
+
output: artifact.nullable(),
|
|
167
|
+
summary: z.object({
|
|
168
|
+
pass: z.boolean(),
|
|
169
|
+
operationCount: z.number().int().nonnegative(),
|
|
170
|
+
appliedCount: z.number().int().nonnegative(),
|
|
171
|
+
}).strict(),
|
|
172
|
+
}).strict();
|
|
173
|
+
|
|
141
174
|
const renderFileIdentity = z.object({
|
|
142
175
|
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
143
176
|
size_bytes: z.number().int().positive(),
|
|
@@ -358,6 +391,18 @@ const tools = [
|
|
|
358
391
|
outputSchema: artifactOutput('xlsx_export_json'),
|
|
359
392
|
handler: xlsxExportJson,
|
|
360
393
|
},
|
|
394
|
+
{
|
|
395
|
+
name: 'xlsx_apply',
|
|
396
|
+
description: 'Apply one deterministic XLSX operations artifact to a current workbook. This tool executes published workbook edits; it does not derive values, coordinates, or business decisions.',
|
|
397
|
+
inputSchema: z.object({
|
|
398
|
+
input: pathInput.describe('Path to the current XLSX workbook.'),
|
|
399
|
+
operations: pathInput.describe('Path to the deterministic tiwater.xlsx-edit/v1 operations artifact.'),
|
|
400
|
+
output: pathInput.describe('New XLSX output path. Existing files are never overwritten.'),
|
|
401
|
+
receiptOutput: pathInput.describe('New JSON receipt path. Existing files are never overwritten.'),
|
|
402
|
+
}).strict(),
|
|
403
|
+
outputSchema: xlsxApplyOutput,
|
|
404
|
+
handler: xlsxApply,
|
|
405
|
+
},
|
|
361
406
|
{
|
|
362
407
|
name: 'xlsx_validate',
|
|
363
408
|
description: 'Validate an XLSX workbook package and return Open XML validation evidence.',
|
|
@@ -961,6 +1006,56 @@ async function xlsxExportJson(args) {
|
|
|
961
1006
|
};
|
|
962
1007
|
}
|
|
963
1008
|
|
|
1009
|
+
async function xlsxApply(args) {
|
|
1010
|
+
const input = path.resolve(requireString(args.input, 'input'));
|
|
1011
|
+
const operations = path.resolve(requireString(args.operations, 'operations'));
|
|
1012
|
+
const output = path.resolve(requireString(args.output, 'output'));
|
|
1013
|
+
const receiptOutput = path.resolve(requireString(args.receiptOutput, 'receiptOutput'));
|
|
1014
|
+
if (path.extname(input).toLowerCase() !== '.xlsx' || path.extname(output).toLowerCase() !== '.xlsx') {
|
|
1015
|
+
throw Object.assign(new Error('XLSX apply input and output must use the .xlsx extension'), { code: -32602 });
|
|
1016
|
+
}
|
|
1017
|
+
await requireNewFile(output, 'output');
|
|
1018
|
+
await requireNewFile(receiptOutput, 'receiptOutput');
|
|
1019
|
+
const inputArtifact = await fileArtifact(input);
|
|
1020
|
+
const operationsArtifact = await fileArtifact(operations);
|
|
1021
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
1022
|
+
try {
|
|
1023
|
+
const result = await runJsonCandidateChain(
|
|
1024
|
+
xlsxCandidates,
|
|
1025
|
+
['edit', input, operations, output],
|
|
1026
|
+
{ allowedExitCodes: [0, 1] });
|
|
1027
|
+
const edit = xlsxEditResult.parse(result.json);
|
|
1028
|
+
if (path.resolve(edit.input) !== input || path.resolve(edit.output) !== output) {
|
|
1029
|
+
throw new Error('XLSX edit receipt is not bound to the current input and output');
|
|
1030
|
+
}
|
|
1031
|
+
const pass = edit.appliedOperations.every(operation => operation.applied);
|
|
1032
|
+
const outputArtifact = pass ? await fileArtifact(output) : null;
|
|
1033
|
+
if (!pass) await rm(output, { force: true });
|
|
1034
|
+
const receipt = xlsxApplyReceipt.parse({
|
|
1035
|
+
schema: 'tiwater.office.xlsx-apply-receipt/v1',
|
|
1036
|
+
pass,
|
|
1037
|
+
input: inputArtifact,
|
|
1038
|
+
operations: operationsArtifact,
|
|
1039
|
+
output: outputArtifact,
|
|
1040
|
+
appliedOperations: edit.appliedOperations,
|
|
1041
|
+
});
|
|
1042
|
+
return {
|
|
1043
|
+
tool: 'xlsx_apply',
|
|
1044
|
+
runtime: commandRuntime(result),
|
|
1045
|
+
receipt: await writeJsonArtifact(receiptOutput, receipt),
|
|
1046
|
+
output: outputArtifact,
|
|
1047
|
+
summary: {
|
|
1048
|
+
pass,
|
|
1049
|
+
operationCount: edit.appliedOperations.length,
|
|
1050
|
+
appliedCount: edit.appliedOperations.filter(operation => operation.applied).length,
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
await rm(output, { force: true });
|
|
1055
|
+
throw error;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
964
1059
|
async function xlsxValidate(args) {
|
|
965
1060
|
const input = requireString(args.input, 'input');
|
|
966
1061
|
const result = await runXlsxValidateCandidateChain(['validate', input]);
|