@tiwater/office-mcp 0.11.0 → 0.12.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
CHANGED
|
@@ -18,19 +18,21 @@ Shared stdio MCP server for Office document workflows.
|
|
|
18
18
|
- `xlsx_validate`
|
|
19
19
|
- `pptx_inspect`
|
|
20
20
|
- `pptx_export_json`
|
|
21
|
+
- `pptx_apply_template`
|
|
22
|
+
- `pptx_apply_format`
|
|
21
23
|
|
|
22
24
|
## Run
|
|
23
25
|
|
|
24
26
|
Install `@tiwater/office-mcp` together with the runtime versions required by
|
|
25
27
|
the consumer, then run `tiwater-office-mcp` as a stdio MCP server.
|
|
26
28
|
|
|
27
|
-
Office MCP 0.
|
|
29
|
+
Office MCP 0.12 requires these minimum published runtimes on `PATH`:
|
|
28
30
|
|
|
29
31
|
| Command | Package | Minimum version |
|
|
30
32
|
| --- | --- | --- |
|
|
31
33
|
| `tiwater-docx` | NuGet `tiwater.docx.cli` | 0.15.0 |
|
|
32
34
|
| `tiwater-xlsx` | NuGet `tiwater.xlsx.cli` | 0.2.55 |
|
|
33
|
-
| `tiwater-pptx` | NuGet `tiwater.pptx.cli` | 0.
|
|
35
|
+
| `tiwater-pptx` | NuGet `tiwater.pptx.cli` | 0.3.1 |
|
|
34
36
|
| `tiwater-convert` | NuGet `tiwater.convert.cli` | 0.9.22 |
|
|
35
37
|
|
|
36
38
|
The server invokes published `tiwater-docx`, `tiwater-xlsx`,
|
|
@@ -52,6 +54,21 @@ complete runtime result in a new receipt artifact. It does not interpret
|
|
|
52
54
|
scenario knowledge or derive workbook edits. Callers independently inspect and
|
|
53
55
|
validate the resulting workbook before delivery.
|
|
54
56
|
|
|
57
|
+
## Presentation editing
|
|
58
|
+
|
|
59
|
+
`pptx_apply_template` and `pptx_apply_format` are orthogonal execution
|
|
60
|
+
operations. The first applies a deterministic template plan to a current
|
|
61
|
+
presentation and selected current template. The second applies deterministic
|
|
62
|
+
format operations to a current presentation. Both bind their inputs, created
|
|
63
|
+
output, and complete runtime result by path and content hash. They do not select
|
|
64
|
+
templates, interpret scenario knowledge, or derive business content, slide
|
|
65
|
+
mappings, geometry, coordinates, or formatting decisions.
|
|
66
|
+
|
|
67
|
+
Callers apply a template first, inspect and validate that intermediate result,
|
|
68
|
+
then build and apply formatting against the intermediate presentation. Final
|
|
69
|
+
inspection, independent readback, native rendering, and delivery closure remain
|
|
70
|
+
separate operations.
|
|
71
|
+
|
|
55
72
|
## Template migration
|
|
56
73
|
|
|
57
74
|
Template migration separates business choice from document mechanics:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "tiwater.office.pptx-format-apply-receipt/v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": ["schema", "pass", "input", "operations", "output", "providerResult"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema": { "const": "tiwater.office.pptx-format-apply-receipt/v1" },
|
|
9
|
+
"pass": { "type": "boolean" },
|
|
10
|
+
"input": { "$ref": "#/$defs/artifact" },
|
|
11
|
+
"operations": { "$ref": "#/$defs/artifact" },
|
|
12
|
+
"output": { "oneOf": [{ "$ref": "#/$defs/artifact" }, { "type": "null" }] },
|
|
13
|
+
"providerResult": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"required": ["input", "output", "operationCount", "changedCount", "changes", "issues"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"input": { "type": "string", "minLength": 1 },
|
|
19
|
+
"output": { "type": "string", "minLength": 1 },
|
|
20
|
+
"operationCount": { "type": "integer", "minimum": 0 },
|
|
21
|
+
"changedCount": { "type": "integer", "minimum": 0 },
|
|
22
|
+
"changes": { "type": "array", "items": { "$ref": "#/$defs/change" } },
|
|
23
|
+
"issues": { "type": "array", "items": { "$ref": "#/$defs/issue" } }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"$defs": {
|
|
28
|
+
"artifact": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": ["path", "sha256", "bytes"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"path": { "type": "string", "minLength": 1 },
|
|
34
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
35
|
+
"bytes": { "type": "integer", "minimum": 0 }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"change": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"required": ["slideNumber", "shapeId", "runIndex", "properties"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"slideNumber": { "type": "integer", "minimum": 1 },
|
|
44
|
+
"shapeId": { "type": "integer", "minimum": 0, "maximum": 4294967295 },
|
|
45
|
+
"runIndex": { "type": "integer", "minimum": 0 },
|
|
46
|
+
"properties": { "type": "array", "items": { "type": "string" } }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"issue": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"required": ["slideNumber", "shapeId", "runIndex", "message"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"slideNumber": { "type": "integer", "minimum": 1 },
|
|
55
|
+
"shapeId": { "type": "integer", "minimum": 0, "maximum": 4294967295 },
|
|
56
|
+
"runIndex": { "type": "integer", "minimum": 0 },
|
|
57
|
+
"message": { "type": "string" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "tiwater.office.pptx-template-apply-receipt/v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": ["schema", "pass", "input", "template", "plan", "output", "providerResult"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema": { "const": "tiwater.office.pptx-template-apply-receipt/v1" },
|
|
9
|
+
"pass": { "type": "boolean" },
|
|
10
|
+
"input": { "$ref": "#/$defs/artifact" },
|
|
11
|
+
"template": { "$ref": "#/$defs/artifact" },
|
|
12
|
+
"plan": { "$ref": "#/$defs/artifact" },
|
|
13
|
+
"output": { "oneOf": [{ "$ref": "#/$defs/artifact" }, { "type": "null" }] },
|
|
14
|
+
"providerResult": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"required": ["input", "template", "output", "changedSlideCount", "issues", "materializedLayoutShapes", "frozenPlaceholderCount", "removedSystemPlaceholders"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"input": { "type": "string", "minLength": 1 },
|
|
20
|
+
"template": { "type": "string", "minLength": 1 },
|
|
21
|
+
"output": { "type": "string", "minLength": 1 },
|
|
22
|
+
"changedSlideCount": { "type": "integer", "minimum": 0 },
|
|
23
|
+
"issues": { "type": "array", "items": { "$ref": "#/$defs/issue" } },
|
|
24
|
+
"materializedLayoutShapes": { "type": "array", "items": { "$ref": "#/$defs/materializedShape" } },
|
|
25
|
+
"frozenPlaceholderCount": { "type": "integer", "minimum": 0 },
|
|
26
|
+
"removedSystemPlaceholders": { "type": "array", "items": { "$ref": "#/$defs/removedPlaceholder" } }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"$defs": {
|
|
31
|
+
"artifact": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"required": ["path", "sha256", "bytes"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"path": { "type": "string", "minLength": 1 },
|
|
37
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
38
|
+
"bytes": { "type": "integer", "minimum": 0 }
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"issue": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"required": ["slideNumber", "message"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"slideNumber": { "type": ["integer", "null"], "minimum": 1 },
|
|
47
|
+
"message": { "type": "string" }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"materializedShape": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["slideNumber", "sourceLayoutPath", "sourceShapeId", "outputShapeId"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"slideNumber": { "type": "integer", "minimum": 1 },
|
|
56
|
+
"sourceLayoutPath": { "type": "string", "minLength": 1 },
|
|
57
|
+
"sourceShapeId": { "type": "integer", "minimum": 0, "maximum": 4294967295 },
|
|
58
|
+
"outputShapeId": { "type": "integer", "minimum": 0, "maximum": 4294967295 }
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"removedPlaceholder": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"required": ["slideNumber", "shapeId", "placeholderType"],
|
|
65
|
+
"properties": {
|
|
66
|
+
"slideNumber": { "type": "integer", "minimum": 1 },
|
|
67
|
+
"shapeId": { "type": "integer", "minimum": 0, "maximum": 4294967295 },
|
|
68
|
+
"placeholderType": { "type": "string", "minLength": 1 }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/office/index.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { createReadStream } from 'node:fs';
|
|
4
4
|
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
6
|
import { isDeepStrictEqual } from 'node:util';
|
|
8
7
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
9
8
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
@@ -171,6 +170,87 @@ const xlsxApplyOutput = z.object({
|
|
|
171
170
|
}).strict(),
|
|
172
171
|
}).strict();
|
|
173
172
|
|
|
173
|
+
const pptxTemplateApplyResult = z.object({
|
|
174
|
+
input: z.string().min(1),
|
|
175
|
+
template: z.string().min(1),
|
|
176
|
+
output: z.string().min(1),
|
|
177
|
+
changedSlideCount: z.number().int().nonnegative(),
|
|
178
|
+
issues: z.array(z.object({
|
|
179
|
+
slideNumber: z.number().int().positive().nullable(),
|
|
180
|
+
message: z.string(),
|
|
181
|
+
}).strict()),
|
|
182
|
+
materializedLayoutShapes: z.array(z.object({
|
|
183
|
+
slideNumber: z.number().int().positive(),
|
|
184
|
+
sourceLayoutPath: z.string().min(1),
|
|
185
|
+
sourceShapeId: z.number().int().nonnegative().max(0xffffffff),
|
|
186
|
+
outputShapeId: z.number().int().nonnegative().max(0xffffffff),
|
|
187
|
+
}).strict()),
|
|
188
|
+
frozenPlaceholderCount: z.number().int().nonnegative(),
|
|
189
|
+
removedSystemPlaceholders: z.array(z.object({
|
|
190
|
+
slideNumber: z.number().int().positive(),
|
|
191
|
+
shapeId: z.number().int().nonnegative().max(0xffffffff),
|
|
192
|
+
placeholderType: z.string().min(1),
|
|
193
|
+
}).strict()),
|
|
194
|
+
}).strict();
|
|
195
|
+
const pptxFormatApplyResult = z.object({
|
|
196
|
+
input: z.string().min(1),
|
|
197
|
+
output: z.string().min(1),
|
|
198
|
+
operationCount: z.number().int().nonnegative(),
|
|
199
|
+
changedCount: z.number().int().nonnegative(),
|
|
200
|
+
changes: z.array(z.object({
|
|
201
|
+
slideNumber: z.number().int().positive(),
|
|
202
|
+
shapeId: z.number().int().nonnegative().max(0xffffffff),
|
|
203
|
+
runIndex: z.number().int().nonnegative(),
|
|
204
|
+
properties: z.array(z.string()),
|
|
205
|
+
}).strict()),
|
|
206
|
+
issues: z.array(z.object({
|
|
207
|
+
slideNumber: z.number().int().positive(),
|
|
208
|
+
shapeId: z.number().int().nonnegative().max(0xffffffff),
|
|
209
|
+
runIndex: z.number().int().nonnegative(),
|
|
210
|
+
message: z.string(),
|
|
211
|
+
}).strict()),
|
|
212
|
+
}).strict();
|
|
213
|
+
const pptxTemplateApplyReceipt = z.object({
|
|
214
|
+
schema: z.literal('tiwater.office.pptx-template-apply-receipt/v1'),
|
|
215
|
+
pass: z.boolean(),
|
|
216
|
+
input: artifact,
|
|
217
|
+
template: artifact,
|
|
218
|
+
plan: artifact,
|
|
219
|
+
output: artifact.nullable(),
|
|
220
|
+
providerResult: pptxTemplateApplyResult,
|
|
221
|
+
}).strict();
|
|
222
|
+
const pptxFormatApplyReceipt = z.object({
|
|
223
|
+
schema: z.literal('tiwater.office.pptx-format-apply-receipt/v1'),
|
|
224
|
+
pass: z.boolean(),
|
|
225
|
+
input: artifact,
|
|
226
|
+
operations: artifact,
|
|
227
|
+
output: artifact.nullable(),
|
|
228
|
+
providerResult: pptxFormatApplyResult,
|
|
229
|
+
}).strict();
|
|
230
|
+
const pptxTemplateApplyOutput = z.object({
|
|
231
|
+
tool: z.literal('pptx_apply_template'),
|
|
232
|
+
runtime: runtimeIdentity,
|
|
233
|
+
receipt: artifact,
|
|
234
|
+
output: artifact.nullable(),
|
|
235
|
+
summary: z.object({
|
|
236
|
+
pass: z.boolean(),
|
|
237
|
+
changedSlideCount: z.number().int().nonnegative(),
|
|
238
|
+
issueCount: z.number().int().nonnegative(),
|
|
239
|
+
}).strict(),
|
|
240
|
+
}).strict();
|
|
241
|
+
const pptxFormatApplyOutput = z.object({
|
|
242
|
+
tool: z.literal('pptx_apply_format'),
|
|
243
|
+
runtime: runtimeIdentity,
|
|
244
|
+
receipt: artifact,
|
|
245
|
+
output: artifact.nullable(),
|
|
246
|
+
summary: z.object({
|
|
247
|
+
pass: z.boolean(),
|
|
248
|
+
operationCount: z.number().int().nonnegative(),
|
|
249
|
+
changedCount: z.number().int().nonnegative(),
|
|
250
|
+
issueCount: z.number().int().nonnegative(),
|
|
251
|
+
}).strict(),
|
|
252
|
+
}).strict();
|
|
253
|
+
|
|
174
254
|
const renderFileIdentity = z.object({
|
|
175
255
|
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
176
256
|
size_bytes: z.number().int().positive(),
|
|
@@ -424,6 +504,31 @@ const tools = [
|
|
|
424
504
|
outputSchema: artifactOutput('pptx_export_json'),
|
|
425
505
|
handler: pptxExportJson,
|
|
426
506
|
},
|
|
507
|
+
{
|
|
508
|
+
name: 'pptx_apply_template',
|
|
509
|
+
description: 'Apply one deterministic PPTX template-application plan to a current presentation. This tool executes the published plan; it does not select a template or derive business content, slide mappings, geometry, or formatting decisions.',
|
|
510
|
+
inputSchema: z.object({
|
|
511
|
+
input: pathInput.describe('Path to the current source PPTX.'),
|
|
512
|
+
template: pathInput.describe('Path to the selected current template PPTX.'),
|
|
513
|
+
plan: pathInput.describe('Path to the deterministic template-application plan artifact.'),
|
|
514
|
+
output: pathInput.describe('New PPTX output path. Existing files are never overwritten.'),
|
|
515
|
+
receiptOutput: pathInput.describe('New JSON receipt path. Existing files are never overwritten.'),
|
|
516
|
+
}).strict(),
|
|
517
|
+
outputSchema: pptxTemplateApplyOutput,
|
|
518
|
+
handler: pptxApplyTemplate,
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'pptx_apply_format',
|
|
522
|
+
description: 'Apply one deterministic PPTX formatting plan to a current presentation. This tool executes published formatting operations; it does not derive values, coordinates, or business decisions.',
|
|
523
|
+
inputSchema: z.object({
|
|
524
|
+
input: pathInput.describe('Path to the current PPTX.'),
|
|
525
|
+
operations: pathInput.describe('Path to the deterministic formatting operations artifact.'),
|
|
526
|
+
output: pathInput.describe('New PPTX output path. Existing files are never overwritten.'),
|
|
527
|
+
receiptOutput: pathInput.describe('New JSON receipt path. Existing files are never overwritten.'),
|
|
528
|
+
}).strict(),
|
|
529
|
+
outputSchema: pptxFormatApplyOutput,
|
|
530
|
+
handler: pptxApplyFormat,
|
|
531
|
+
},
|
|
427
532
|
];
|
|
428
533
|
|
|
429
534
|
function buildServer() {
|
|
@@ -1058,7 +1163,7 @@ async function xlsxApply(args) {
|
|
|
1058
1163
|
|
|
1059
1164
|
async function xlsxValidate(args) {
|
|
1060
1165
|
const input = requireString(args.input, 'input');
|
|
1061
|
-
const result = await
|
|
1166
|
+
const result = await runJsonCandidateChain(xlsxCandidates, ['validate', input], { allowedExitCodes: [0, 1] });
|
|
1062
1167
|
return { tool: 'xlsx_validate', runtime: commandRuntime(result), result: result.json };
|
|
1063
1168
|
}
|
|
1064
1169
|
|
|
@@ -1082,6 +1187,81 @@ async function pptxExportJson(args) {
|
|
|
1082
1187
|
};
|
|
1083
1188
|
}
|
|
1084
1189
|
|
|
1190
|
+
async function pptxApplyTemplate(args) {
|
|
1191
|
+
return pptxApply('pptx_apply_template', args, true);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
async function pptxApplyFormat(args) {
|
|
1195
|
+
return pptxApply('pptx_apply_format', args, false);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
async function pptxApply(tool, args, templateMode) {
|
|
1199
|
+
const input = path.resolve(requireString(args.input, 'input'));
|
|
1200
|
+
const template = templateMode ? path.resolve(requireString(args.template, 'template')) : null;
|
|
1201
|
+
const plan = path.resolve(requireString(templateMode ? args.plan : args.operations, templateMode ? 'plan' : 'operations'));
|
|
1202
|
+
const output = path.resolve(requireString(args.output, 'output'));
|
|
1203
|
+
const receiptOutput = path.resolve(requireString(args.receiptOutput, 'receiptOutput'));
|
|
1204
|
+
for (const [label, candidate] of [['input', input], ['output', output], ...(template ? [['template', template]] : [])]) {
|
|
1205
|
+
if (path.extname(candidate).toLowerCase() !== '.pptx') {
|
|
1206
|
+
throw Object.assign(new Error(`${label} must use the .pptx extension`), { code: -32602 });
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
await requireNewFile(output, 'output');
|
|
1210
|
+
await requireNewFile(receiptOutput, 'receiptOutput');
|
|
1211
|
+
const inputArtifact = await fileArtifact(input);
|
|
1212
|
+
const templateArtifact = template ? await fileArtifact(template) : null;
|
|
1213
|
+
const planArtifact = await fileArtifact(plan);
|
|
1214
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
1215
|
+
try {
|
|
1216
|
+
const command = templateMode ? 'apply-template' : 'apply-format-edits';
|
|
1217
|
+
const commandArgs = templateMode
|
|
1218
|
+
? [command, input, template, plan, output]
|
|
1219
|
+
: [command, input, plan, output];
|
|
1220
|
+
const result = await runJsonCandidateChain(pptxCandidates, commandArgs, { allowedExitCodes: [0, 1] });
|
|
1221
|
+
await requireArtifactUnchanged(inputArtifact, 'PPTX apply input');
|
|
1222
|
+
if (templateArtifact) await requireArtifactUnchanged(templateArtifact, 'PPTX apply template');
|
|
1223
|
+
await requireArtifactUnchanged(planArtifact, 'PPTX apply plan');
|
|
1224
|
+
const providerResult = (templateMode ? pptxTemplateApplyResult : pptxFormatApplyResult).parse(result.json);
|
|
1225
|
+
if (path.resolve(providerResult.input) !== input
|
|
1226
|
+
|| path.resolve(providerResult.output) !== output
|
|
1227
|
+
|| (templateMode && path.resolve(providerResult.template) !== template)) {
|
|
1228
|
+
throw new Error('PPTX apply receipt is not bound to the current inputs and output');
|
|
1229
|
+
}
|
|
1230
|
+
const pass = providerResult.issues.length === 0;
|
|
1231
|
+
const outputArtifact = pass ? await fileArtifact(output) : null;
|
|
1232
|
+
if (!pass) await rm(output, { force: true });
|
|
1233
|
+
const receiptSchema = templateMode ? pptxTemplateApplyReceipt : pptxFormatApplyReceipt;
|
|
1234
|
+
const receipt = receiptSchema.parse({
|
|
1235
|
+
schema: templateMode
|
|
1236
|
+
? 'tiwater.office.pptx-template-apply-receipt/v1'
|
|
1237
|
+
: 'tiwater.office.pptx-format-apply-receipt/v1',
|
|
1238
|
+
pass,
|
|
1239
|
+
input: inputArtifact,
|
|
1240
|
+
...(templateMode ? { template: templateArtifact } : {}),
|
|
1241
|
+
...(templateMode ? { plan: planArtifact } : { operations: planArtifact }),
|
|
1242
|
+
output: outputArtifact,
|
|
1243
|
+
providerResult,
|
|
1244
|
+
});
|
|
1245
|
+
return {
|
|
1246
|
+
tool,
|
|
1247
|
+
runtime: commandRuntime(result),
|
|
1248
|
+
receipt: await writeJsonArtifact(receiptOutput, receipt),
|
|
1249
|
+
output: outputArtifact,
|
|
1250
|
+
summary: templateMode
|
|
1251
|
+
? { pass, changedSlideCount: providerResult.changedSlideCount, issueCount: providerResult.issues.length }
|
|
1252
|
+
: { pass, operationCount: providerResult.operationCount, changedCount: providerResult.changedCount, issueCount: providerResult.issues.length },
|
|
1253
|
+
};
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
await rm(output, { force: true });
|
|
1256
|
+
throw error;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function requireArtifactUnchanged(expected, label) {
|
|
1261
|
+
const current = await fileArtifact(expected.path);
|
|
1262
|
+
if (!isDeepStrictEqual(current, expected)) throw new Error(`${label} changed during provider execution`);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1085
1265
|
async function writeJsonArtifact(output, payload) {
|
|
1086
1266
|
const fullPath = path.resolve(output);
|
|
1087
1267
|
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
@@ -1125,59 +1305,3 @@ function commandRuntime(result) {
|
|
|
1125
1305
|
}
|
|
1126
1306
|
|
|
1127
1307
|
serveStdio(buildServer);
|
|
1128
|
-
|
|
1129
|
-
async function runXlsxValidateCandidateChain(args) {
|
|
1130
|
-
const errors = [];
|
|
1131
|
-
for (const candidate of xlsxCandidates) {
|
|
1132
|
-
try {
|
|
1133
|
-
const result = await runValidationCommand(candidate, args);
|
|
1134
|
-
const text = result.stdout.trim();
|
|
1135
|
-
if (!text) return { ...result, json: null };
|
|
1136
|
-
try {
|
|
1137
|
-
return { ...result, json: JSON.parse(text) };
|
|
1138
|
-
} catch {
|
|
1139
|
-
if (result.code !== 0) {
|
|
1140
|
-
errors.push(`${candidate.command}: validate did not return JSON`);
|
|
1141
|
-
continue;
|
|
1142
|
-
}
|
|
1143
|
-
throw new Error(`Expected JSON output but received: ${text.slice(0, 300)}${text.length > 300 ? '…' : ''}`);
|
|
1144
|
-
}
|
|
1145
|
-
} catch (error) {
|
|
1146
|
-
if (error?.code === 'ENOENT') {
|
|
1147
|
-
errors.push(`${candidate.command}: not found`);
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
throw error;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
throw new Error(`No runnable command candidate succeeded. ${errors.join('; ')}`);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
async function runValidationCommand(candidate, args) {
|
|
1157
|
-
const env = { ...process.env, ...(candidate.env || {}) };
|
|
1158
|
-
const cwd = candidate.cwd || process.cwd();
|
|
1159
|
-
const commandArgs = [...(candidate.argsPrefix || []), ...args];
|
|
1160
|
-
|
|
1161
|
-
return await new Promise((resolve, reject) => {
|
|
1162
|
-
const child = spawn(candidate.command, commandArgs, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1163
|
-
let stdout = '';
|
|
1164
|
-
let stderr = '';
|
|
1165
|
-
|
|
1166
|
-
child.stdout.on('data', chunk => {
|
|
1167
|
-
stdout += chunk.toString();
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
child.stderr.on('data', chunk => {
|
|
1171
|
-
stderr += chunk.toString();
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
child.on('error', reject);
|
|
1175
|
-
child.on('close', code => {
|
|
1176
|
-
if (code === 0 || code === 1) {
|
|
1177
|
-
resolve({ code, stdout, stderr, command: candidate.command, args: commandArgs, cwd });
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
reject(new Error(`${candidate.command} ${commandArgs.join(' ')} failed with exit code ${code}\n${stderr || stdout}`));
|
|
1181
|
-
});
|
|
1182
|
-
});
|
|
1183
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiwater/office-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Published MCP server for Tiwater Office document capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"_shared/tool-runtime.mjs",
|
|
20
20
|
"office/index.mjs",
|
|
21
|
-
"office/README.md"
|
|
21
|
+
"office/README.md",
|
|
22
|
+
"office/contracts/*.schema.json"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
25
|
"test": "node --test office/*.test.mjs"
|
|
@@ -29,5 +30,8 @@
|
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@modelcontextprotocol/server": "2.0.0",
|
|
31
32
|
"zod": "4.4.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"ajv": "8.20.0"
|
|
32
36
|
}
|
|
33
37
|
}
|