@tiwater/office-mcp 0.21.34 → 0.21.36

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.
@@ -70,6 +70,17 @@ export function assertEffectKindToolContract(tool, expectedKind = undefined) {
70
70
  return metadata.kind;
71
71
  }
72
72
 
73
+ export function documentMutationFileArguments(tool, args) {
74
+ assertEffectKindToolContract(tool, 'document-mutation');
75
+ const bindings = boundFileArguments(tool?.inputSchema, args);
76
+ const current = bindings.filter(binding => binding.revisionRole === 'current');
77
+ const effectiveOutput = bindings.filter(binding => binding.role === 'write' && binding.effect);
78
+ if (current.length !== 1 || effectiveOutput.length !== 1) {
79
+ throw new Error(`document-mutation-file-arguments-invalid:${tool?.name || 'unnamed'}`);
80
+ }
81
+ return { current: current[0].value, effectiveOutput: effectiveOutput[0].value };
82
+ }
83
+
73
84
  function fileBindings(schema) {
74
85
  const bindings = [];
75
86
  function visit(node) {
@@ -91,3 +102,35 @@ function fileBindings(schema) {
91
102
  visit(schema);
92
103
  return bindings;
93
104
  }
105
+
106
+ function boundFileArguments(schema, value) {
107
+ const bindings = [];
108
+ function visit(node, currentValue) {
109
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return;
110
+ if (node['x-tiwater-file-role'] === 'read' || node['x-tiwater-file-role'] === 'write') {
111
+ if (typeof currentValue !== 'string' || currentValue.length === 0) {
112
+ throw new Error('provider-file-argument-invalid');
113
+ }
114
+ bindings.push({
115
+ role: node['x-tiwater-file-role'],
116
+ effect: node['x-tiwater-file-role'] === 'write'
117
+ && node['x-tiwater-file-effect'] !== false,
118
+ revisionRole: node[documentRevisionRoleKey],
119
+ value: currentValue,
120
+ });
121
+ return;
122
+ }
123
+ if (Array.isArray(currentValue)) {
124
+ for (const entry of currentValue) visit(node.items, entry);
125
+ } else if (currentValue && typeof currentValue === 'object') {
126
+ for (const [name, child] of Object.entries(node.properties || {})) {
127
+ if (Object.hasOwn(currentValue, name)) visit(child, currentValue[name]);
128
+ }
129
+ }
130
+ for (const keyword of ['allOf', 'anyOf', 'oneOf']) {
131
+ for (const child of node[keyword] || []) visit(child, currentValue);
132
+ }
133
+ }
134
+ visit(schema, value);
135
+ return bindings;
136
+ }
@@ -21,17 +21,10 @@
21
21
  "required": ["part", "path"],
22
22
  "additionalProperties": false
23
23
  },
24
- "limit": {
25
- "type": "integer",
26
- "minimum": 1,
27
- "maximum": 9007199254740991,
28
- "description": "Maximum number of table rows requested in this response page. The provider may return fewer to keep the machine response bounded."
29
- },
30
- "offset": {
31
- "type": "integer",
32
- "minimum": 0,
33
- "maximum": 9007199254740991,
34
- "description": "Zero-based row offset. Continue only when an unreturned row is needed for the current operation; unused blank template rows need not be read."
24
+ "continuation": {
25
+ "type": "string",
26
+ "minLength": 1,
27
+ "description": "Opaque continuation returned by the preceding page. Pass it back unchanged; omit it for the first page."
35
28
  },
36
29
  "output": {
37
30
  "type": "string",
@@ -6,12 +6,13 @@
6
6
  "type": "string",
7
7
  "minLength": 1,
8
8
  "description": "Current target DOCX containing every target paragraph or table cell.",
9
- "x-tiwater-file-role": "read", "x-tiwater-document-revision-role": "current"
9
+ "x-tiwater-file-role": "read",
10
+ "x-tiwater-document-revision-role": "current"
10
11
  },
11
12
  "changes": {
12
13
  "minItems": 1,
13
14
  "type": "array",
14
- "description": "Atomic source-to-target content replacements. Every source selection is resolved and validated before the target document is changed.",
15
+ "description": "Replace one or more existing target paragraphs or cells with explicit native source selections in one atomic commit. This preserves selected native runs but does not change rows, spans, or merges.",
15
16
  "items": {
16
17
  "type": "object",
17
18
  "properties": {
@@ -44,7 +45,7 @@
44
45
  "sourceSelections": {
45
46
  "minItems": 1,
46
47
  "type": "array",
47
- "description": "Ordered native source cells, paragraphs, runs, or text nodes. Consecutive run or text selections from one source paragraph form one target paragraph; a range retains the native runs it crosses. A source paragraph boundary remains a target paragraph boundary. Selected content becomes the target content without Agent transcription.",
48
+ "description": "Ordered native source cells, paragraphs, runs, or text nodes. The target becomes exactly these selections; omitted source content is omitted. When excluding parallel-language text, explicitly include every required language-neutral formula, number, unit, and symbol in source order. Consecutive selections from one source paragraph form one target paragraph; a range retains the native runs it crosses. A source paragraph boundary remains a target paragraph boundary. Selected content becomes the target content without Agent transcription.",
48
49
  "items": {
49
50
  "type": "object",
50
51
  "properties": {
@@ -0,0 +1,107 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "tiwater.office-mcp-input/docx_set_table",
4
+ "type": "object",
5
+ "properties": {
6
+ "input": { "type": "string", "minLength": 1, "x-tiwater-file-role": "read", "x-tiwater-document-revision-role": "current" },
7
+ "table": {
8
+ "type": "object",
9
+ "description": "Current target table address returned by docx_read_table or docx_table_index.",
10
+ "properties": {
11
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
12
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
13
+ },
14
+ "required": ["part", "path"], "additionalProperties": false
15
+ },
16
+ "existingRows": {
17
+ "type": "object",
18
+ "description": "Exact contiguous current row range to replace. Rows outside this range stay unchanged, and the range must enclose every vertical merge it intersects.",
19
+ "properties": {
20
+ "first": {
21
+ "type": "object", "properties": {
22
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
23
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
24
+ }, "required": ["part", "path"], "additionalProperties": false
25
+ },
26
+ "last": {
27
+ "type": "object", "properties": {
28
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
29
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
30
+ }, "required": ["part", "path"], "additionalProperties": false
31
+ }
32
+ }, "required": ["first", "last"], "additionalProperties": false
33
+ },
34
+ "columns": {
35
+ "type": "array", "minItems": 1,
36
+ "description": "Complete target table grid in native order. IDs are request-local names used only by rows below.",
37
+ "items": {
38
+ "type": "object", "properties": {
39
+ "id": { "type": "string", "minLength": 1, "maxLength": 80 },
40
+ "gridColumn": {
41
+ "type": "object", "properties": {
42
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
43
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
44
+ }, "required": ["part", "path"], "additionalProperties": false
45
+ }
46
+ }, "required": ["id", "gridColumn"], "additionalProperties": false
47
+ }
48
+ },
49
+ "rows": {
50
+ "type": "array", "minItems": 0,
51
+ "description": "Final logical rows. Explicit cells plus active row spans cover the target grid exactly once.",
52
+ "items": {
53
+ "type": "object", "properties": {
54
+ "prototypeRow": {
55
+ "type": "object", "properties": {
56
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
57
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
58
+ }, "required": ["part", "path"], "additionalProperties": false
59
+ },
60
+ "cells": {
61
+ "type": "array", "minItems": 1,
62
+ "items": {
63
+ "type": "object", "properties": {
64
+ "columns": { "type": "array", "minItems": 1, "description": "One or more contiguous request-local column IDs occupied by this cell.", "items": { "type": "string", "minLength": 1, "maxLength": 80 } },
65
+ "rowSpan": { "type": "integer", "minimum": 1, "maximum": 2147483647 },
66
+ "text": { "anyOf": [{ "type": "string" }, { "type": "null" }], "description": "Final derived plain text, or null when sourceContent is used." },
67
+ "sourceContent": {
68
+ "anyOf": [{
69
+ "type": "object",
70
+ "properties": {
71
+ "input": { "type": "string", "minLength": 1, "x-tiwater-file-role": "read" },
72
+ "selections": {
73
+ "type": "array", "minItems": 1,
74
+ "items": {
75
+ "type": "object", "properties": {
76
+ "address": {
77
+ "type": "object", "properties": {
78
+ "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
79
+ "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
80
+ }, "required": ["part", "path"], "additionalProperties": false
81
+ },
82
+ "range": {
83
+ "type": "object", "properties": {
84
+ "start": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
85
+ "length": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }
86
+ }, "required": ["start", "length"], "additionalProperties": false
87
+ }
88
+ }, "required": ["address"], "additionalProperties": false
89
+ }
90
+ }
91
+ }, "required": ["input", "selections"], "additionalProperties": false
92
+ }, { "type": "null" }],
93
+ "description": "Exact ordered native source selections, or null when text is used."
94
+ }
95
+ }, "required": ["columns", "text", "sourceContent"], "additionalProperties": false
96
+ }
97
+ },
98
+ "cantSplit": { "type": "boolean" }
99
+ }, "required": ["prototypeRow", "cells"], "additionalProperties": false
100
+ }
101
+ },
102
+ "output": { "type": "string", "minLength": 1, "description": "Output DOCX path; may equal input for one atomic in-place update.", "x-tiwater-file-role": "write" },
103
+ "receiptOutput": { "type": "string", "minLength": 1, "x-tiwater-file-role": "write", "x-tiwater-file-effect": false }
104
+ },
105
+ "required": ["input", "table", "existingRows", "columns", "rows", "output", "receiptOutput"],
106
+ "additionalProperties": false
107
+ }
@@ -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.34"
5
+ "version": "0.21.36"
6
6
  },
7
7
  "tools": [
8
8
  {
@@ -71,17 +71,6 @@
71
71
  "sha256": "995eb1a5a8992e2a2186f2d7621b5eea7875eecd78ce153e77964d2942db2211"
72
72
  }
73
73
  },
74
- {
75
- "name": "docx_fill_table_from_tables",
76
- "providerContract": {
77
- "source": "packages/docx-cli/contracts/mcp-input/docx_fill_table_from_tables.schema.json",
78
- "sha256": "81d335ffaf20b896ae043e96a5a83f1beb3debbaf7404a99c8b4f06a62989c7d"
79
- },
80
- "inputContract": {
81
- "path": "office/contracts/docx_fill_table_from_tables.schema.json",
82
- "sha256": "81d335ffaf20b896ae043e96a5a83f1beb3debbaf7404a99c8b4f06a62989c7d"
83
- }
84
- },
85
74
  {
86
75
  "name": "docx_insert_objects",
87
76
  "providerContract": {
@@ -152,11 +141,11 @@
152
141
  "name": "docx_read_table",
153
142
  "providerContract": {
154
143
  "source": "packages/docx-cli/contracts/mcp-input/docx_read_table.schema.json",
155
- "sha256": "6f69cef86035d3905cb84b2b9c4a6af3ac66179be4122f82543fe7acacef288f"
144
+ "sha256": "0b7a5669daeaa6f8e171ba01542b4bfd065cd282a32943e7b5b5854551b4afc7"
156
145
  },
157
146
  "inputContract": {
158
147
  "path": "office/contracts/docx_read_table.schema.json",
159
- "sha256": "6f69cef86035d3905cb84b2b9c4a6af3ac66179be4122f82543fe7acacef288f"
148
+ "sha256": "0b7a5669daeaa6f8e171ba01542b4bfd065cd282a32943e7b5b5854551b4afc7"
160
149
  }
161
150
  },
162
151
  {
@@ -174,11 +163,11 @@
174
163
  "name": "docx_replace_content_from_source",
175
164
  "providerContract": {
176
165
  "source": "packages/docx-cli/contracts/mcp-input/docx_replace_content_from_source.schema.json",
177
- "sha256": "beca665f61a19911e8c4cb17dc5bad27051c5561e8b31dd839f8825bee7e6385"
166
+ "sha256": "04740981ad30190be4625ce3a2640b48abec02a9bc0eccc3e0e7d4fe40dbc6e0"
178
167
  },
179
168
  "inputContract": {
180
169
  "path": "office/contracts/docx_replace_content_from_source.schema.json",
181
- "sha256": "beca665f61a19911e8c4cb17dc5bad27051c5561e8b31dd839f8825bee7e6385"
170
+ "sha256": "04740981ad30190be4625ce3a2640b48abec02a9bc0eccc3e0e7d4fe40dbc6e0"
182
171
  }
183
172
  },
184
173
  {
@@ -204,14 +193,14 @@
204
193
  }
205
194
  },
206
195
  {
207
- "name": "docx_set_table_body",
196
+ "name": "docx_set_table",
208
197
  "providerContract": {
209
- "source": "packages/docx-cli/contracts/mcp-input/docx_set_table_body.schema.json",
210
- "sha256": "a22d7d38cd3bad30372d45e289e3dc4700a3e52715fec990ac4644888e92ac5e"
198
+ "source": "packages/docx-cli/contracts/mcp-input/docx_set_table.schema.json",
199
+ "sha256": "1b501654b6cd3ee58831393c9100ac37e1e5c21a5a92071a6f612b103aae122e"
211
200
  },
212
201
  "inputContract": {
213
- "path": "office/contracts/docx_set_table_body.schema.json",
214
- "sha256": "a22d7d38cd3bad30372d45e289e3dc4700a3e52715fec990ac4644888e92ac5e"
202
+ "path": "office/contracts/docx_set_table.schema.json",
203
+ "sha256": "1b501654b6cd3ee58831393c9100ac37e1e5c21a5a92071a6f612b103aae122e"
215
204
  }
216
205
  },
217
206
  {
package/office/index.mjs CHANGED
@@ -24,7 +24,10 @@ import {
24
24
  } from '../_shared/large-json-result.mjs';
25
25
  import { withOutputWriteLock } from '../_shared/output-write-lock.mjs';
26
26
  import { evidenceRoleMetadata } from '../_shared/evidence-role.mjs';
27
- import { effectKindMetadata } from '../_shared/effect-kind.mjs';
27
+ import {
28
+ documentMutationFileArguments,
29
+ effectKindMetadata,
30
+ } from '../_shared/effect-kind.mjs';
28
31
  import { compactDocxObjectIdentity } from './docx-object-identity.mjs';
29
32
 
30
33
  const packageMetadata = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
@@ -38,14 +41,20 @@ if (inputContractManifest.provider?.id !== packageMetadata.name
38
41
  }
39
42
  const inputContracts = new Map(await Promise.all(inputContractManifest.tools.map(async entry => {
40
43
  const schema = JSON.parse(await readFile(new URL(`./contracts/${entry.name}.schema.json`, import.meta.url), 'utf8'));
41
- return [entry.name, z.fromJSONSchema(schema)];
44
+ return [entry.name, { schema, validator: z.fromJSONSchema(schema) }];
42
45
  })));
43
46
  const invocationCwd = process.cwd();
44
47
 
45
48
  function inputContract(toolName) {
46
49
  const contract = inputContracts.get(toolName);
47
50
  if (!contract) throw new Error(`Missing provider-owned MCP input contract: ${toolName}`);
48
- return contract;
51
+ return contract.validator;
52
+ }
53
+
54
+ function inputContractSchema(toolName) {
55
+ const contract = inputContracts.get(toolName);
56
+ if (!contract) throw new Error(`Missing provider-owned MCP input contract: ${toolName}`);
57
+ return contract.schema;
49
58
  }
50
59
 
51
60
  const docxCandidates = [
@@ -151,14 +160,14 @@ const xlsxFixedTools = [
151
160
  {"name":"xlsx_set_column_width","description":"Set current worksheet column widths."},
152
161
  ];
153
162
 
154
- function fixedToolDefinitions(definitions) {
163
+ function fixedToolDefinitions(definitions, candidates) {
155
164
  return definitions.map(definition => ({
156
165
  name: definition.name,
157
166
  effectKind: 'document-mutation',
158
167
  description: definition.description,
159
168
  inputSchema: inputContract(definition.name),
160
169
  outputSchema: fixedEditOutput(definition.name),
161
- handler: args => fixedEdit(definition.name, args),
170
+ handler: (args, tool) => fixedEdit(tool, args, candidates),
162
171
  }));
163
172
  }
164
173
 
@@ -372,9 +381,10 @@ const docxTableReadOutput = docxObservationOutput('docx_read_table').extend({
372
381
  receipt: z.object({
373
382
  schema: z.literal('tiwater.docx-table-page-receipt/v1'),
374
383
  totalRowCount: z.number().int().nonnegative(),
384
+ retainedRowCount: z.number().int().nonnegative(),
375
385
  returnedRowCount: z.number().int().nonnegative(),
376
386
  remaining: z.number().int().nonnegative(),
377
- nextOffset: z.number().int().nonnegative().optional(),
387
+ nextContinuation: z.string().min(1).optional(),
378
388
  detailPageRetained: z.boolean(),
379
389
  narrowingRequired: z.boolean(),
380
390
  }).strict(),
@@ -460,7 +470,7 @@ const tools = [
460
470
  },
461
471
  {
462
472
  name: 'docx_read_table',
463
- description: 'Read an explicit row range from exactly one table selected by native OpenXML address; it never builds another whole-table data object. Set returnContent true to return a byte-bounded compact row page. Provide output to store full paragraph and text-node detail, including native verticalTextAlignment, for the selected row page and return its artifact receipt. These channels are independent and may be used together; at least one is required. Request only rows needed for the current decision; receipt.remaining is navigation information, not an obligation to read unused rows, and blank template rows need not be paged through. receipt.nextOffset is present only when another row page exists. Each returned row and cell keeps its reusable native address, zero-based logical gridColumnStart, gridSpan, vertical-merge owner, physical text, and logical text. Match columns across rows by gridColumnStart; a tc[n] path or array position is only that row\'s physical cell ordinal and is not a column identity when gridSpan or gridBefore is present. In a vertical merge, restart begins one logical cell and a continue cell is not an independent row value: logicalText resolves the restart cell value while text remains the physical cell value. Use docx_read_object when one exact object needs a narrower descendant view. The provider reports physical structure only; the Agent decides template and business meaning.',
473
+ description: 'Read one explicit native DOCX table. Provide output to retain every remaining row with full paragraph and text-node detail. Set returnContent true to also receive the largest compact inline page within the response limit; these channels are independent and at least one is required. retainedRowCount counts artifact rows and returnedRowCount counts inline rows. When remaining is nonzero, continue only by passing receipt.nextContinuation unchanged in the next call; a continuation page depends on the preceding receipt and cannot be predicted or read in parallel. Match columns by gridColumnStart, not tc position. A vertical-merge restart owns one logical value; a continue cell points to verticalMergeOwner and does not repeat that value inline.',
464
474
  inputSchema: inputContract('docx_read_table'),
465
475
  outputSchema: docxTableReadOutput,
466
476
  annotations: { readOnlyHint: true, idempotentHint: true },
@@ -469,10 +479,10 @@ const tools = [
469
479
  {
470
480
  name: 'docx_replace_content_from_source',
471
481
  effectKind: 'document-mutation',
472
- description: 'Replace existing target paragraph or table-cell content from explicitly selected native source cells, paragraphs, runs, text nodes, or exact text ranges while retaining target container formatting and table structure. For a source cell already returned by docx_read_table, select a Unicode-scalar range directly against its returned text; the provider retains every crossed native run, including superscript and subscript, without another descendant read. Consecutive run or text selections from one source paragraph form one target paragraph, and source paragraph boundaries remain paragraph boundaries. Use it after docx_fill_table_from_tables when the target needs selected source content instead of the whole source cell; pass returned addresses unchanged and never retype source-owned text. It does not copy source rows, cells, spans, or merges.',
482
+ description: 'Atomically replace one or more existing target paragraphs or cells with exactly the selected native source content. Select source cells, paragraphs, runs, text nodes, or Unicode-scalar text ranges; omitted content is omitted, while selected runs retain superscript, subscript, formulas, numbers, units, and symbols. This tool does not change table rows, spans, or merges.',
473
483
  inputSchema: inputContract('docx_replace_content_from_source'),
474
484
  outputSchema: fixedEditOutput('docx_replace_content_from_source'),
475
- handler: args => fixedEdit('docx_replace_content_from_source', args),
485
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
476
486
  },
477
487
  {
478
488
  name: 'docx_set_text',
@@ -480,7 +490,7 @@ const tools = [
480
490
  description: 'Replace the whole text content of paragraph or cell objects observed from this exact input DOCX while retaining target formatting, bookmarks, spans, and vertical merges. For a vertically merged logical cell, write its visible text to the restart cell rather than a continue cell. Tabs and line breaks remain native document text controls; targets containing non-text objects are rejected. Use this only for newly derived text. Content copied or selected from a source DOCX uses docx_replace_content_from_source so native runs such as superscript and subscript are retained. This does not insert objects, change table structure, copy source formatting, or decide business wording.',
481
491
  inputSchema: inputContract('docx_set_text'),
482
492
  outputSchema: fixedEditOutput('docx_set_text'),
483
- handler: args => fixedEdit('docx_set_text', args),
493
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
484
494
  },
485
495
  {
486
496
  name: 'docx_set_paragraph_pagination',
@@ -488,23 +498,15 @@ const tools = [
488
498
  description: 'Set native pagination properties on explicitly selected current DOCX paragraphs. Each change sets at least one pagination property. keepWithNext keeps a paragraph with the immediately following paragraph or table but does not guarantee that a table header remains with its first body row. keepLinesTogether keeps one paragraph on one page; pageBreakBefore starts it on a new page; preventWidowOrphanLines controls isolated first or last lines. Omitted properties remain unchanged. The caller chooses paragraphs from current native addresses; the provider does not decide document layout or business meaning.',
489
499
  inputSchema: inputContract('docx_set_paragraph_pagination'),
490
500
  outputSchema: fixedEditOutput('docx_set_paragraph_pagination'),
491
- handler: args => fixedEdit('docx_set_paragraph_pagination', args),
501
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
492
502
  },
493
503
  {
494
- name: 'docx_set_table_body',
504
+ name: 'docx_set_table',
495
505
  effectKind: 'document-mutation',
496
- description: 'Atomically replace one exact current target-table row range while retaining the table, target styles, grid widths, and all rows and surrounding content outside the range. When retaining leading rows reported with repeatHeader=true, existingRows starts after all of them and never at a verticalMerge=continue row. Name every target grid column in native order and choose one current row inside existingRows as the style prototype for each final row. Horizontal spans use contiguous column IDs. Set rowSpan on one logical cell to occupy multiple rows and omit those columns from the covered rows; the provider writes native vertical merge cells. Set cantSplit on a final physical row when it must remain whole across page boundaries; omit it to preserve the prototype row property. Every other grid column remains explicit. Every explicit cell includes an already-derived value; source-owned content is not retyped here. 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 source tables, map source to target, derive text, choose business columns, identify headers, or accept non-text cell content; use docx_fill_table_from_tables and docx_replace_content_from_source for source-owned table content.',
497
- inputSchema: inputContract('docx_set_table_body'),
498
- outputSchema: fixedEditOutput('docx_set_table_body'),
499
- handler: args => fixedEdit('docx_set_table_body', args),
500
- },
501
- {
502
- name: 'docx_fill_table_from_tables',
503
- effectKind: 'document-mutation',
504
- description: 'Fill one current target-table body from one or more explicitly ordered current source-table row ranges. For each source, select an unmerged record column when every physical source row must remain an output row; mapped columns still retain their native vertical merges. Select a merged record column only when every mapping into one target cell has one equal scalar value throughout that merge group. Map source grid columns onto every target prototype cell. The provider concatenates source records in declared order, copies each mapped source cell in full, preserves horizontal spans, rebuilds vertical merges only within each source range, validates the complete target grid, commits once, and returns structural readback. It does not discover source tables, choose source or target business meaning, filter records, translate or rewrite text, or apply business-specific rules. A single source uses the same sources array with one item. If a target needs only selected source descendants, such as one language from a bilingual cell, immediately use the returned target-cell addresses with docx_replace_content_from_source before releasing that source or reading back the completed target.',
505
- inputSchema: inputContract('docx_fill_table_from_tables'),
506
- outputSchema: fixedEditOutput('docx_fill_table_from_tables'),
507
- handler: args => fixedEdit('docx_fill_table_from_tables', args),
506
+ description: 'Atomically replace one exact current target-table row range with a fully specified table body. Name every target grid column in native order. Each explicit cell occupies contiguous columns and may span logical rows; covered columns are omitted from following rows. Set each cell from either derived text or exact native source selections, never both. Native source selections retain run formatting such as superscript and subscript. The provider retains the target table, target cell styles, grid widths, and all content outside the replaced range, and exposes no intermediate document. It does not select source rows, map business columns, infer target shape, derive wording, or copy a source table wholesale.',
507
+ inputSchema: inputContract('docx_set_table'),
508
+ outputSchema: fixedEditOutput('docx_set_table'),
509
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
508
510
  },
509
511
  {
510
512
  name: 'docx_insert_objects',
@@ -512,7 +514,7 @@ const tools = [
512
514
  description: 'Insert selected current DOCX objects under an existing parent. Table rows are objects: expand a target table by copying one contiguous observed row range and use repeat for count; sourceInput may equal input. A row range beginning with vertical-merge continuations may be inserted only inside a target boundary with the same active grid spans, which extends those merges. Individual table cells are not raw insertion targets because that would bypass the table grid.',
513
515
  inputSchema: inputContract('docx_insert_objects'),
514
516
  outputSchema: fixedEditOutput('docx_insert_objects'),
515
- handler: args => fixedEdit('docx_insert_objects', args),
517
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
516
518
  },
517
519
  {
518
520
  name: 'docx_delete_object',
@@ -520,7 +522,7 @@ const tools = [
520
522
  description: 'Delete selected current DOCX objects directly from the current target document. Selected table rows must close every vertical merge and cannot remove the whole table. Individual table cells are not raw deletion targets; use column or merge operations for table structure.',
521
523
  inputSchema: inputContract('docx_delete_object'),
522
524
  outputSchema: fixedEditOutput('docx_delete_object'),
523
- handler: args => fixedEdit('docx_delete_object', args),
525
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
524
526
  },
525
527
  {
526
528
  name: 'docx_merge_cells',
@@ -528,7 +530,7 @@ const tools = [
528
530
  description: 'Merge selected current DOCX cells when they form one closed rectangle. A one-column, multi-row rectangle creates a vertical merge whose first cell is the restart owner and whose later cells are continuations. All selected cell content moves into the top-left owner, so the selected content must already be correct for that one logical cell.',
529
531
  inputSchema: inputContract('docx_merge_cells'),
530
532
  outputSchema: fixedEditOutput('docx_merge_cells'),
531
- handler: args => fixedEdit('docx_merge_cells', args),
533
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
532
534
  },
533
535
  {
534
536
  name: 'docx_split_cells',
@@ -536,7 +538,7 @@ const tools = [
536
538
  description: 'Split selected current DOCX merged cells.',
537
539
  inputSchema: inputContract('docx_split_cells'),
538
540
  outputSchema: fixedEditOutput('docx_split_cells'),
539
- handler: args => fixedEdit('docx_split_cells', args),
541
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
540
542
  },
541
543
  {
542
544
  name: 'docx_insert_table_columns',
@@ -544,7 +546,7 @@ const tools = [
544
546
  description: 'Insert empty template-shaped grid columns into one current main-document table. Select an observed source grid column for width and per-row cell formatting, and optionally a before grid-column address; cells spanning the insertion boundary expand instead of being split. It does not copy business values or decide column meaning.',
545
547
  inputSchema: inputContract('docx_insert_table_columns'),
546
548
  outputSchema: fixedEditOutput('docx_insert_table_columns'),
547
- handler: args => fixedEdit('docx_insert_table_columns', args),
549
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
548
550
  },
549
551
  {
550
552
  name: 'docx_delete_table_columns',
@@ -552,7 +554,7 @@ const tools = [
552
554
  description: 'Delete selected observed grid columns from one current main-document table while shrinking spanning cells and preserving the remaining table grid. It cannot remove every column and does not decide whether a business column is unused.',
553
555
  inputSchema: inputContract('docx_delete_table_columns'),
554
556
  outputSchema: fixedEditOutput('docx_delete_table_columns'),
555
- handler: args => fixedEdit('docx_delete_table_columns', args),
557
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
556
558
  },
557
559
  {
558
560
  name: 'docx_compare',
@@ -593,7 +595,7 @@ const tools = [
593
595
  description: 'Apply one explicit font family and size policy to current main-document body and table text. It does not derive a policy or alter other run semantics.',
594
596
  inputSchema: inputContract('docx_apply_font_policy'),
595
597
  outputSchema: fixedEditOutput('docx_apply_font_policy'),
596
- handler: args => fixedEdit('docx_apply_font_policy', args),
598
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
597
599
  },
598
600
  {
599
601
  name: 'docx_validate_toc_style_policy',
@@ -609,7 +611,7 @@ const tools = [
609
611
  description: 'Apply explicit italic and per-level indentation values to current built-in table-of-contents paragraph styles. It does not change heading text or refresh fields.',
610
612
  inputSchema: inputContract('docx_apply_toc_style_policy'),
611
613
  outputSchema: fixedEditOutput('docx_apply_toc_style_policy'),
612
- handler: args => fixedEdit('docx_apply_toc_style_policy', args),
614
+ handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
613
615
  },
614
616
  {
615
617
  name: 'docx_refresh_fields',
@@ -676,7 +678,7 @@ const tools = [
676
678
  annotations: { readOnlyHint: true, idempotentHint: true },
677
679
  handler: xlsxReadRange,
678
680
  },
679
- ...fixedToolDefinitions(xlsxFixedTools),
681
+ ...fixedToolDefinitions(xlsxFixedTools, xlsxCandidates),
680
682
  {
681
683
  name: 'xlsx_validate',
682
684
  description: 'Validate an XLSX workbook package and produce OpenXML validation evidence. Set returnContent true to return the complete result when it fits the response limit. Provide output to write the complete result to a new JSON file. The two choices are independent and may be used together; at least one is required.',
@@ -709,7 +711,7 @@ const tools = [
709
711
  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.',
710
712
  inputSchema: inputContract('pptx_apply_template'),
711
713
  outputSchema: fixedEditOutput('pptx_apply_template'),
712
- handler: args => fixedEdit('pptx_apply_template', args),
714
+ handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
713
715
  },
714
716
  {
715
717
  name: 'pptx_apply_format',
@@ -717,7 +719,7 @@ const tools = [
717
719
  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.',
718
720
  inputSchema: inputContract('pptx_apply_format'),
719
721
  outputSchema: fixedEditOutput('pptx_apply_format'),
720
- handler: args => fixedEdit('pptx_apply_format', args),
722
+ handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
721
723
  },
722
724
  {
723
725
  name: 'pptx_set_shape_geometry',
@@ -725,7 +727,7 @@ const tools = [
725
727
  description: 'Set exact native EMU bounds for uniquely identified current-slide PPTX objects. One call batches only this fixed geometry action and does not infer repair coordinates.',
726
728
  inputSchema: inputContract('pptx_set_shape_geometry'),
727
729
  outputSchema: fixedEditOutput('pptx_set_shape_geometry'),
728
- handler: args => fixedEdit('pptx_set_shape_geometry', args),
730
+ handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
729
731
  },
730
732
  {
731
733
  name: 'pptx_replace_picture_image',
@@ -733,7 +735,7 @@ const tools = [
733
735
  description: 'Replace embedded PNG or JPEG media for uniquely identified current-slide PPTX pictures while preserving the picture object, geometry, crop, and unrelated media. One call batches only this fixed replacement action.',
734
736
  inputSchema: inputContract('pptx_replace_picture_image'),
735
737
  outputSchema: fixedEditOutput('pptx_replace_picture_image'),
736
- handler: args => fixedEdit('pptx_replace_picture_image', args),
738
+ handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
737
739
  },
738
740
  {
739
741
  name: 'pptx_validate',
@@ -769,8 +771,8 @@ function buildServer() {
769
771
  },
770
772
  async args => {
771
773
  const payload = typeof args.output === 'string'
772
- ? await withOutputWriteLock(args.output, () => tool.handler(args))
773
- : await tool.handler(args);
774
+ ? await withOutputWriteLock(args.output, () => tool.handler(args, tool))
775
+ : await tool.handler(args, tool);
774
776
  return createToolResult(payload, { isError: payload?.summary?.pass === false });
775
777
  },
776
778
  );
@@ -853,6 +855,34 @@ async function docxInspect(args) {
853
855
  };
854
856
  }
855
857
 
858
+ function encodeTableContinuation(input, table, offset) {
859
+ return Buffer.from(JSON.stringify({
860
+ schema: 'tiwater.docx-table-continuation/v1',
861
+ input,
862
+ table,
863
+ offset,
864
+ })).toString('base64url');
865
+ }
866
+
867
+ function decodeTableContinuation(continuation, input, table) {
868
+ if (continuation === undefined) return 0;
869
+ let decoded;
870
+ try {
871
+ decoded = JSON.parse(Buffer.from(continuation, 'base64url').toString('utf8'));
872
+ } catch {
873
+ throw new Error('invalid-docx-table-continuation');
874
+ }
875
+ if (decoded?.schema !== 'tiwater.docx-table-continuation/v1'
876
+ || decoded.input !== input
877
+ || decoded.table?.part !== table?.part
878
+ || decoded.table?.path !== table?.path
879
+ || !Number.isSafeInteger(decoded.offset)
880
+ || decoded.offset < 0) {
881
+ throw new Error('invalid-docx-table-continuation');
882
+ }
883
+ return decoded.offset;
884
+ }
885
+
856
886
  async function docxObservation(tool, args) {
857
887
  const input = path.resolve(requireString(args.input, 'input'));
858
888
  const delivery = resultChannels(args);
@@ -947,18 +977,20 @@ async function docxObservation(tool, args) {
947
977
  }
948
978
  if (tool === 'docx_read_table') {
949
979
  const totalRowCount = payload.rows.length;
950
- const offset = Math.min(args.offset ?? 0, totalRowCount);
951
- const requestedLimit = args.limit ?? totalRowCount;
952
- const selectedRows = payload.rows.slice(offset, offset + requestedLimit);
980
+ const offset = Math.min(decodeTableContinuation(args.continuation, input, args.table), totalRowCount);
981
+ const selectedRows = payload.rows.slice(offset);
953
982
  const selectedNextOffset = offset + selectedRows.length < totalRowCount
954
983
  ? offset + selectedRows.length
955
984
  : null;
956
985
  const selectedPageReceipt = {
957
986
  schema: 'tiwater.docx-table-page-receipt/v1',
958
987
  totalRowCount,
988
+ retainedRowCount: selectedRows.length,
959
989
  returnedRowCount: selectedRows.length,
960
990
  remaining: totalRowCount - offset - selectedRows.length,
961
- ...(selectedNextOffset === null ? {} : { nextOffset: selectedNextOffset }),
991
+ ...(selectedNextOffset === null ? {} : {
992
+ nextContinuation: encodeTableContinuation(input, args.table, selectedNextOffset),
993
+ }),
962
994
  };
963
995
  const detailPage = {
964
996
  schema: 'tiwater.docx-table-detail-page/v1',
@@ -987,6 +1019,7 @@ async function docxObservation(tool, args) {
987
1019
  followingParagraph: payload.followingParagraph,
988
1020
  receipt: {
989
1021
  ...selectedPageReceipt,
1022
+ returnedRowCount: 0,
990
1023
  detailPageRetained: true,
991
1024
  narrowingRequired: false,
992
1025
  },
@@ -1008,7 +1041,7 @@ async function docxObservation(tool, args) {
1008
1041
  verticalMerge: cell.verticalMerge,
1009
1042
  verticalMergeOwner: cell.verticalMergeOwner,
1010
1043
  text: cell.paragraphs.map(paragraph => paragraph.text).join('\n'),
1011
- logicalText: cell.logicalText,
1044
+ logicalText: cell.verticalMerge === 'continue' ? '' : cell.logicalText,
1012
1045
  })),
1013
1046
  };
1014
1047
  const candidate = [...rows, row];
@@ -1018,15 +1051,19 @@ async function docxObservation(tool, args) {
1018
1051
  }
1019
1052
  rows.push(row);
1020
1053
  }
1021
- const nextOffset = !narrowingRequired && offset + rows.length < totalRowCount
1054
+ const retainedRowCount = delivery.output === null ? rows.length : selectedRows.length;
1055
+ const inlineNextOffset = offset + rows.length < totalRowCount
1022
1056
  ? offset + rows.length
1023
1057
  : null;
1024
1058
  const pageReceipt = {
1025
1059
  schema: 'tiwater.docx-table-page-receipt/v1',
1026
1060
  totalRowCount,
1061
+ retainedRowCount,
1027
1062
  returnedRowCount: rows.length,
1028
1063
  remaining: totalRowCount - offset - rows.length,
1029
- ...(nextOffset === null ? {} : { nextOffset }),
1064
+ ...(inlineNextOffset === null ? {} : {
1065
+ nextContinuation: encodeTableContinuation(input, args.table, inlineNextOffset),
1066
+ }),
1030
1067
  };
1031
1068
  return {
1032
1069
  ...withArtifact,
@@ -1040,7 +1077,7 @@ async function docxObservation(tool, args) {
1040
1077
  receipt: {
1041
1078
  ...pageReceipt,
1042
1079
  detailPageRetained: delivery.output !== null,
1043
- narrowingRequired,
1080
+ narrowingRequired: delivery.output === null && narrowingRequired,
1044
1081
  },
1045
1082
  rows,
1046
1083
  };
@@ -1165,28 +1202,37 @@ async function copyTransform(tool, candidates, command, args, suffix = []) {
1165
1202
  }
1166
1203
  }
1167
1204
 
1168
- async function fixedEdit(tool, args) {
1169
- const input = path.resolve(requireString(args.input, 'input'));
1170
- const output = path.resolve(requireString(args.output, 'output'));
1205
+ async function fixedEdit(tool, args, candidates) {
1206
+ const publishedContract = {
1207
+ name: tool.name,
1208
+ inputSchema: inputContractSchema(tool.name),
1209
+ annotations: tool.annotations,
1210
+ _meta: effectKindMetadata(tool.effectKind),
1211
+ };
1212
+ const bindings = documentMutationFileArguments(publishedContract, args);
1213
+ const input = path.resolve(bindings.current);
1214
+ const output = path.resolve(bindings.effectiveOutput);
1171
1215
  const receiptOutput = path.resolve(requireString(args.receiptOutput, 'receiptOutput'));
1172
- if (!(tool.startsWith('docx_') && input === output)) await requireNewFile(output, 'output');
1216
+ if (input !== output) await requireNewFile(output, 'output');
1173
1217
  await requireNewFile(receiptOutput, 'receiptOutput');
1174
- const candidates = tool.startsWith('docx_') ? docxCandidates
1175
- : tool.startsWith('xlsx_') ? xlsxCandidates
1176
- : pptxCandidates;
1177
1218
  return withTempJsonFile(args, async requestPath => {
1178
- const result = await runJsonCandidateChain(candidates, [tool, requestPath], { allowedExitCodes: [0, 1] });
1219
+ const result = await runJsonCandidateChain(candidates, [tool.name, requestPath], { allowedExitCodes: [0, 1] });
1179
1220
  if (result.code !== 0) {
1180
- const detail = result.stderr.trim() || result.stdout.trim() || `${tool} failed with exit code ${result.code}`;
1221
+ const detail = result.stderr.trim() || result.stdout.trim()
1222
+ || `${tool.name} failed with exit code ${result.code}`;
1181
1223
  throw new Error(detail);
1182
1224
  }
1183
- if (result.json?.tool !== tool) throw new Error(`${tool} returned a mismatched tool identity`);
1225
+ if (result.json?.tool !== tool.name) throw new Error(`${tool.name} returned a mismatched tool identity`);
1184
1226
  await requireReturnedArtifact(result.json.receipt, receiptOutput, 'receipt');
1185
1227
  if (result.json.output === null) {
1186
- if (result.json.summary?.pass !== false) throw new Error(`${tool} omitted output without reporting failure`);
1228
+ if (result.json.summary?.pass !== false) {
1229
+ throw new Error(`${tool.name} omitted output without reporting failure`);
1230
+ }
1187
1231
  } else {
1188
1232
  await requireReturnedArtifact(result.json.output, output, 'output');
1189
- if (result.json.summary?.pass !== true) throw new Error(`${tool} returned output without reporting success`);
1233
+ if (result.json.summary?.pass !== true) {
1234
+ throw new Error(`${tool.name} returned output without reporting success`);
1235
+ }
1190
1236
  }
1191
1237
  return { ...result.json, runtime: commandRuntime(result) };
1192
1238
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiwater/office-mcp",
3
- "version": "0.21.34",
3
+ "version": "0.21.36",
4
4
  "description": "Published MCP distribution for independent Tiwater Office, PDF, and Text document capabilities",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  "schema": "tiwater.pdf-provider-contract-manifest/v1",
3
3
  "provider": {
4
4
  "id": "@tiwater/office-mcp",
5
- "version": "0.21.34"
5
+ "version": "0.21.36"
6
6
  },
7
7
  "runtime": {
8
8
  "command": "tiwater-pdf"
@@ -2,7 +2,7 @@
2
2
  "schema": "tiwater.text-provider-contract-manifest/v1",
3
3
  "provider": {
4
4
  "id": "@tiwater/office-mcp",
5
- "version": "0.21.34"
5
+ "version": "0.21.36"
6
6
  },
7
7
  "tools": [
8
8
  {
@@ -1,162 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "tiwater.office-mcp-input/docx_fill_table_from_tables",
4
- "description": "Project source-table rows into one target template table atomically. Use this instead of manually rebuilding rows when target cells derive from source tables. Target formatting is reused, and native vertical-merge owners are preserved within each source range.",
5
- "type": "object",
6
- "properties": {
7
- "input": { "type": "string", "minLength": 1, "description": "Current target DOCX.", "x-tiwater-file-role": "read", "x-tiwater-document-revision-role": "current" },
8
- "table": {
9
- "type": "object",
10
- "description": "Current target table address returned by docx_read_table or docx_table_index.",
11
- "properties": {
12
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
13
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
14
- },
15
- "required": ["part", "path"],
16
- "additionalProperties": false
17
- },
18
- "existingRows": {
19
- "type": "object",
20
- "description": "Exact contiguous current target row range to replace; it must enclose every vertical merge it intersects.",
21
- "properties": {
22
- "first": {
23
- "type": "object",
24
- "properties": {
25
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
26
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
27
- },
28
- "required": ["part", "path"],
29
- "additionalProperties": false
30
- },
31
- "last": {
32
- "type": "object",
33
- "properties": {
34
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
35
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
36
- },
37
- "required": ["part", "path"],
38
- "additionalProperties": false
39
- }
40
- },
41
- "required": ["first", "last"],
42
- "additionalProperties": false
43
- },
44
- "prototypeRow": {
45
- "type": "object",
46
- "description": "One current row inside existingRows whose cell formatting is reused for every generated body row.",
47
- "properties": {
48
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
49
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
50
- },
51
- "required": ["part", "path"],
52
- "additionalProperties": false
53
- },
54
- "sources": {
55
- "type": "array",
56
- "minItems": 1,
57
- "description": "Ordered source-table ranges whose logical records are concatenated into the target body. Source boundaries never create a vertical merge with one another.",
58
- "items": {
59
- "type": "object",
60
- "properties": {
61
- "input": { "type": "string", "minLength": 1, "description": "Current source DOCX.", "x-tiwater-file-role": "read" },
62
- "table": {
63
- "type": "object",
64
- "description": "Current source table address.",
65
- "properties": {
66
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
67
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
68
- },
69
- "required": ["part", "path"],
70
- "additionalProperties": false
71
- },
72
- "rows": {
73
- "type": "object",
74
- "description": "Exact contiguous source data-row range. Intersecting native vertical merges are clipped to this range, retain their owner content, and restart at the first selected row.",
75
- "properties": {
76
- "first": {
77
- "type": "object",
78
- "properties": {
79
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
80
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
81
- },
82
- "required": ["part", "path"],
83
- "additionalProperties": false
84
- },
85
- "last": {
86
- "type": "object",
87
- "properties": {
88
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
89
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
90
- },
91
- "required": ["part", "path"],
92
- "additionalProperties": false
93
- }
94
- },
95
- "required": ["first", "last"],
96
- "additionalProperties": false
97
- },
98
- "recordColumn": {
99
- "type": "object",
100
- "description": "Source grid-column address that defines output-row records. Choose an unmerged column when every physical source row must remain an output row; vertical merges in other mapped columns are still preserved. Choose a merged column only when every mapping into one target cell has one equal scalar value throughout that merge group.",
101
- "properties": {
102
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
103
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
104
- },
105
- "required": ["part", "path"],
106
- "additionalProperties": false
107
- },
108
- "columnMappings": {
109
- "type": "array",
110
- "minItems": 1,
111
- "description": "Complete projection from this source grid onto all target prototype cells. Multiple adjacent source columns may compose one target cell; one source column may spread its logical values across several target cells.",
112
- "items": {
113
- "type": "object",
114
- "properties": {
115
- "sourceColumns": {
116
- "type": "array",
117
- "minItems": 1,
118
- "description": "One or more contiguous source grid columns in native order. Multiple columns compose one target cell; a source cell spanning several selected columns contributes once.",
119
- "items": {
120
- "type": "object",
121
- "properties": {
122
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
123
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
124
- },
125
- "required": ["part", "path"],
126
- "additionalProperties": false
127
- }
128
- },
129
- "targetColumns": {
130
- "type": "array",
131
- "minItems": 1,
132
- "description": "One or more contiguous target grid columns covering complete prototype cells.",
133
- "items": {
134
- "type": "object",
135
- "properties": {
136
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
137
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
138
- },
139
- "required": ["part", "path"],
140
- "additionalProperties": false
141
- }
142
- },
143
- "joinWith": {
144
- "type": "string",
145
- "description": "Separator used when sourceColumns compose one target cell. Defaults to a newline."
146
- }
147
- },
148
- "required": ["sourceColumns", "targetColumns"],
149
- "additionalProperties": false
150
- }
151
- }
152
- },
153
- "required": ["input", "table", "rows", "recordColumn", "columnMappings"],
154
- "additionalProperties": false
155
- }
156
- },
157
- "output": { "type": "string", "minLength": 1, "description": "Output DOCX path; may equal input for one atomic in-place update.", "x-tiwater-file-role": "write" },
158
- "receiptOutput": { "type": "string", "minLength": 1, "x-tiwater-file-role": "write", "x-tiwater-file-effect": false }
159
- },
160
- "required": ["input", "table", "existingRows", "prototypeRow", "sources", "output", "receiptOutput"],
161
- "additionalProperties": false
162
- }
@@ -1,136 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "tiwater.office-mcp-input/docx_set_table_body",
4
- "type": "object",
5
- "properties": {
6
- "input": {
7
- "type": "string",
8
- "minLength": 1,
9
- "x-tiwater-file-role": "read", "x-tiwater-document-revision-role": "current"
10
- },
11
- "table": {
12
- "type": "object",
13
- "properties": {
14
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
15
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
16
- },
17
- "required": ["part", "path"],
18
- "additionalProperties": false,
19
- "description": "Current target table address returned by docx_read_table or docx_table_index."
20
- },
21
- "existingRows": {
22
- "type": "object",
23
- "description": "Exact contiguous current row range to replace. Rows outside this range stay unchanged, and the range must enclose every vertical merge it intersects.",
24
- "properties": {
25
- "first": {
26
- "type": "object",
27
- "properties": {
28
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
29
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
30
- },
31
- "required": ["part", "path"],
32
- "additionalProperties": false
33
- },
34
- "last": {
35
- "type": "object",
36
- "properties": {
37
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
38
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
39
- },
40
- "required": ["part", "path"],
41
- "additionalProperties": false
42
- }
43
- },
44
- "required": ["first", "last"],
45
- "additionalProperties": false
46
- },
47
- "columns": {
48
- "type": "array",
49
- "minItems": 1,
50
- "description": "Complete target table grid in the native order returned by docx_read_table. IDs are request-local names used only by rows below.",
51
- "items": {
52
- "type": "object",
53
- "properties": {
54
- "id": { "type": "string", "minLength": 1, "maxLength": 80 },
55
- "gridColumn": {
56
- "type": "object",
57
- "properties": {
58
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
59
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
60
- },
61
- "required": ["part", "path"],
62
- "additionalProperties": false
63
- }
64
- },
65
- "required": ["id", "gridColumn"],
66
- "additionalProperties": false
67
- }
68
- },
69
- "rows": {
70
- "type": "array",
71
- "minItems": 0,
72
- "description": "Final logical rows; an empty array removes the range when another row remains. Cells plus active row spans cover the complete target grid exactly once.",
73
- "items": {
74
- "type": "object",
75
- "properties": {
76
- "prototypeRow": {
77
- "type": "object",
78
- "properties": {
79
- "part": { "type": "string", "minLength": 1, "pattern": "^\\/" },
80
- "path": { "type": "string", "pattern": "^\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\](?:\\/[A-Za-z_][A-Za-z0-9_.-]*:[A-Za-z_][A-Za-z0-9_.-]*\\[[1-9][0-9]*\\])*$" }
81
- },
82
- "required": ["part", "path"],
83
- "additionalProperties": false
84
- },
85
- "cells": {
86
- "type": "array",
87
- "minItems": 1,
88
- "items": {
89
- "type": "object",
90
- "properties": {
91
- "columns": {
92
- "type": "array",
93
- "minItems": 1,
94
- "description": "One or more contiguous request-local column IDs occupied by this cell.",
95
- "items": { "type": "string", "minLength": 1, "maxLength": 80 }
96
- },
97
- "text": {
98
- "type": "string",
99
- "description": "Final plain text for this cell. If the current cell at the same row and grid span already has exactly this text, its native paragraphs and run formatting are retained while row and merge properties are updated."
100
- },
101
- "rowSpan": {
102
- "type": "integer",
103
- "minimum": 1,
104
- "maximum": 2147483647,
105
- "description": "Logical row count occupied by this cell; omitted means one. Later rows omit these columns."
106
- }
107
- },
108
- "required": ["columns", "text"],
109
- "additionalProperties": false
110
- }
111
- },
112
- "cantSplit": {
113
- "type": "boolean",
114
- "description": "Whether Word may split this physical row across pages. Omit to preserve the prototype row property."
115
- }
116
- },
117
- "required": ["prototypeRow", "cells"],
118
- "additionalProperties": false
119
- }
120
- },
121
- "output": {
122
- "type": "string",
123
- "minLength": 1,
124
- "description": "Output DOCX path; may equal input for one atomic in-place update.",
125
- "x-tiwater-file-role": "write"
126
- },
127
- "receiptOutput": {
128
- "type": "string",
129
- "minLength": 1,
130
- "x-tiwater-file-role": "write",
131
- "x-tiwater-file-effect": false
132
- }
133
- },
134
- "required": ["input", "table", "existingRows", "columns", "rows", "output", "receiptOutput"],
135
- "additionalProperties": false
136
- }