@tiwater/office-mcp 0.21.4 → 0.21.6

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.
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { createReadStream } from 'node:fs';
3
- import { mkdir, stat, writeFile } from 'node:fs/promises';
3
+ import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
 
6
6
  import { requireString } from './tool-runtime.mjs';
@@ -31,7 +31,7 @@ export async function deliverLargeJsonResult({ tool, args, runtime, payload, sou
31
31
  runtime,
32
32
  sources: await Promise.all(sourcePaths.map(fileArtifact)),
33
33
  returnContent: channels.returnContent,
34
- artifact: channels.output === null ? null : await writeJsonArtifact(channels.output, payload),
34
+ artifact: channels.output === null ? null : await writeIdempotentJsonArtifact(channels.output, payload),
35
35
  receipt: {
36
36
  contentBytes,
37
37
  contentReturned,
@@ -47,6 +47,28 @@ export async function writeJsonArtifact(output, payload) {
47
47
  const bytes = Buffer.from(`${JSON.stringify(payload, null, 2)}\n`, 'utf8');
48
48
  await mkdir(path.dirname(fullPath), { recursive: true });
49
49
  await writeFile(fullPath, bytes, { flag: 'wx' });
50
+ return describeJsonArtifact(fullPath, bytes);
51
+ }
52
+
53
+ export async function writeIdempotentJsonArtifact(output, payload) {
54
+ const fullPath = path.resolve(output);
55
+ const bytes = Buffer.from(`${JSON.stringify(payload, null, 2)}\n`, 'utf8');
56
+ await mkdir(path.dirname(fullPath), { recursive: true });
57
+ try {
58
+ await writeFile(fullPath, bytes, { flag: 'wx' });
59
+ } catch (error) {
60
+ if (error?.code !== 'EEXIST') throw error;
61
+ const existing = await readFile(fullPath);
62
+ if (!existing.equals(bytes)) {
63
+ throw Object.assign(new Error(`artifact-path-already-exists-with-different-content:${fullPath}`), {
64
+ code: 'EEXIST',
65
+ });
66
+ }
67
+ }
68
+ return describeJsonArtifact(fullPath, bytes);
69
+ }
70
+
71
+ function describeJsonArtifact(fullPath, bytes) {
50
72
  return {
51
73
  path: fullPath,
52
74
  sha256: createHash('sha256').update(bytes).digest('hex'),
@@ -69,7 +69,7 @@
69
69
  "rows": {
70
70
  "type": "array",
71
71
  "minItems": 0,
72
- "description": "Exact final rows; an empty array removes the declared range when at least one table row remains. Each final row names one current row in existingRows as its style prototype. Cells plus active row spans must cover the complete target grid exactly once.",
72
+ "description": "Exact final physical rows; an empty array removes the declared range when at least one table row remains. Each final row names one current row in existingRows as its style prototype, and its cells cover the complete target grid exactly once.",
73
73
  "items": {
74
74
  "type": "object",
75
75
  "properties": {
@@ -95,11 +95,10 @@
95
95
  "items": { "type": "string", "minLength": 1, "maxLength": 80 }
96
96
  },
97
97
  "text": { "type": "string" },
98
- "rowSpan": {
99
- "type": "integer",
100
- "minimum": 1,
101
- "maximum": 2147483647,
102
- "description": "Number of final rows occupied by this cell; omitted means one row."
98
+ "verticalMerge": {
99
+ "type": "string",
100
+ "enum": ["restart", "continue"],
101
+ "description": "Native OpenXML vertical merge state. A restart cell owns the text; the same columns in the next physical row use continue with empty text. Omit for an unmerged cell."
103
102
  }
104
103
  },
105
104
  "required": ["columns", "text"],
@@ -2,7 +2,7 @@
2
2
  "schema": "tiwater.office-provider-contract-manifest/v1",
3
3
  "provider": {
4
4
  "id": "@tiwater/office-mcp",
5
- "version": "0.21.4"
5
+ "version": "0.21.6"
6
6
  },
7
7
  "tools": [
8
8
  {
@@ -185,11 +185,11 @@
185
185
  "name": "docx_set_table_body",
186
186
  "providerContract": {
187
187
  "source": "packages/docx-cli/contracts/mcp-input/docx_set_table_body.schema.json",
188
- "sha256": "2270274f25154dc0573c972c8c71240636e29e7e23a1eacae616952cc600b1e0"
188
+ "sha256": "e1f6c96d71dc42904fb1d0c8b049c1398e1155d76db481b58064cede9e30fad6"
189
189
  },
190
190
  "inputContract": {
191
191
  "path": "office/contracts/docx_set_table_body.schema.json",
192
- "sha256": "2270274f25154dc0573c972c8c71240636e29e7e23a1eacae616952cc600b1e0"
192
+ "sha256": "e1f6c96d71dc42904fb1d0c8b049c1398e1155d76db481b58064cede9e30fad6"
193
193
  }
194
194
  },
195
195
  {
package/office/index.mjs CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  fileArtifact,
20
20
  resultChannels,
21
21
  returnedContentBudgetBytes,
22
+ writeIdempotentJsonArtifact,
22
23
  writeJsonArtifact,
23
24
  } from '../_shared/large-json-result.mjs';
24
25
  import { withOutputWriteLock } from '../_shared/output-write-lock.mjs';
@@ -423,7 +424,7 @@ const tools = [
423
424
  },
424
425
  {
425
426
  name: 'docx_set_table_body',
426
- description: 'Atomically replace one exact current target-table row range with final text rows while retaining the table, target styles, grid widths, and all rows and surrounding content outside the range. Name every target grid column in native order, choose one current row as the style prototype for each final row, and express horizontal spans with contiguous column IDs and vertical spans with rowSpan. An empty final row array removes the range when another table row remains. The provider commits once and returns structural readback. It does not read a source table, map source to target, derive text, choose business columns, identify headers, or accept non-text cell content.',
427
+ description: 'Atomically replace one exact current target-table row range with final physical text rows while retaining the table, target styles, grid widths, and all rows and surrounding content outside the range. Name every target grid column in native order, choose one current row as the style prototype for each final row, and cover every row completely with explicit cells. Horizontal spans use contiguous column IDs. Native vertical merges use a restart cell followed by continue cells on the same columns; continue cells have empty physical text. An empty final row array removes the range when another table row remains. The provider commits once and returns structural readback. It does not read a source table, map source to target, derive text, choose business columns, identify headers, or accept non-text cell content.',
427
428
  inputSchema: inputContract('docx_set_table_body'),
428
429
  outputSchema: fixedEditOutput('docx_set_table_body'),
429
430
  handler: args => fixedEdit('docx_set_table_body', args),
@@ -746,7 +747,7 @@ async function docxInspect(args) {
746
747
  returnContent: delivery.returnContent,
747
748
  artifact: delivery.output === null
748
749
  ? null
749
- : await writeJsonArtifact(delivery.output, result.json),
750
+ : await writeIdempotentJsonArtifact(delivery.output, result.json),
750
751
  ...(delivery.returnContent ? compactDocxInspection(result.json) : {}),
751
752
  };
752
753
  }
@@ -770,7 +771,7 @@ async function docxObservation(tool, args) {
770
771
  const offset = Math.min(args.offset ?? 0, totalCount);
771
772
  const withArtifact = delivery.output === null ? retained : {
772
773
  ...retained,
773
- artifact: await writeJsonArtifact(delivery.output, result.json),
774
+ artifact: await writeIdempotentJsonArtifact(delivery.output, result.json),
774
775
  };
775
776
  if (!delivery.returnContent) {
776
777
  return {
@@ -805,7 +806,7 @@ async function docxObservation(tool, args) {
805
806
  const totalCount = payload.tables.length;
806
807
  const withArtifact = delivery.output === null ? retained : {
807
808
  ...retained,
808
- artifact: await writeJsonArtifact(delivery.output, result.json),
809
+ artifact: await writeIdempotentJsonArtifact(delivery.output, result.json),
809
810
  };
810
811
  if (!delivery.returnContent) {
811
812
  return {
@@ -872,7 +873,7 @@ async function docxObservation(tool, args) {
872
873
  };
873
874
  const withArtifact = delivery.output === null ? retained : {
874
875
  ...retained,
875
- artifact: await writeJsonArtifact(delivery.output, detailPage),
876
+ artifact: await writeIdempotentJsonArtifact(delivery.output, detailPage),
876
877
  };
877
878
  if (!delivery.returnContent) {
878
879
  return {
@@ -964,7 +965,7 @@ async function docxReadObject(args) {
964
965
  returnContent: delivery.returnContent,
965
966
  artifact: delivery.output === null
966
967
  ? null
967
- : await writeJsonArtifact(delivery.output, result.json),
968
+ : await writeIdempotentJsonArtifact(delivery.output, result.json),
968
969
  receipt: {
969
970
  schema: 'tiwater.docx-read-object-receipt/v1',
970
971
  observationCount: observations.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiwater/office-mcp",
3
- "version": "0.21.4",
3
+ "version": "0.21.6",
4
4
  "description": "Published MCP server for Tiwater Office document capabilities",
5
5
  "type": "module",
6
6
  "license": "MIT",