@ffschrattenecker/tm1-mcp-server 7.0.2 → 8.0.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/CHANGELOG.md +83 -1
- package/README.md +5 -5
- package/dist/config.js +34 -17
- package/dist/connections.d.ts +18 -1
- package/dist/connections.js +34 -18
- package/dist/http-transport.js +51 -4
- package/dist/lib/callgraph/referenceIndex.js +2 -1
- package/dist/lib/callgraph/rulesLinter.d.ts +0 -19
- package/dist/lib/callgraph/rulesLinter.js +0 -590
- package/dist/lib/callgraph/tiParser.js +10 -5
- package/dist/lib/callgraph/tm1-adapter.d.ts +5 -1
- package/dist/lib/callgraph/tm1-adapter.js +41 -9
- package/dist/lib/callgraph/variableEnv.js +3 -2
- package/dist/lib/cell-address.d.ts +1 -1
- package/dist/lib/cell-address.js +2 -2
- package/dist/lib/complexity/antipatterns.js +3 -1
- package/dist/lib/complexity/comment-classifier.js +2 -1
- package/dist/lib/feeders/element-type-cache.js +4 -6
- package/dist/lib/naming/odata-filter.js +2 -1
- package/dist/lib/pro-parser.js +13 -4
- package/dist/lib/safe-regex.d.ts +0 -13
- package/dist/lib/safe-regex.js +40 -13
- package/dist/lib/ti-identifier.d.ts +11 -0
- package/dist/lib/ti-identifier.js +11 -0
- package/dist/lib/tm1-name.d.ts +9 -0
- package/dist/lib/tm1-name.js +9 -0
- package/dist/lib/v12-compat/deprecated-ti.js +2 -2
- package/dist/session-manager.d.ts +1 -0
- package/dist/session-manager.js +24 -0
- package/dist/tm1-client/connection/profile.js +2 -4
- package/dist/tm1-client/dispatcher.d.ts +1 -1
- package/dist/tm1-client/dispatcher.js +18 -6
- package/dist/tm1-client/http.d.ts +21 -0
- package/dist/tm1-client/http.js +39 -30
- package/dist/tm1-client/services/cell-service.d.ts +9 -7
- package/dist/tm1-client/services/cell-service.js +51 -25
- package/dist/tm1-client/services/chore-service.js +8 -9
- package/dist/tm1-client/services/cube-service.d.ts +14 -18
- package/dist/tm1-client/services/cube-service.js +51 -45
- package/dist/tm1-client/services/dimension-order.d.ts +13 -0
- package/dist/tm1-client/services/dimension-order.js +42 -0
- package/dist/tm1-client/services/dimension-service.js +4 -6
- package/dist/tm1-client/services/element-service.d.ts +35 -11
- package/dist/tm1-client/services/element-service.js +124 -60
- package/dist/tm1-client/services/file-service.d.ts +42 -6
- package/dist/tm1-client/services/file-service.js +225 -28
- package/dist/tm1-client/services/hierarchy-service.d.ts +19 -9
- package/dist/tm1-client/services/hierarchy-service.js +118 -12
- package/dist/tm1-client/services/monitoring-service.js +2 -5
- package/dist/tm1-client/services/odata-page.d.ts +6 -0
- package/dist/tm1-client/services/odata-page.js +8 -0
- package/dist/tm1-client/services/process-service.d.ts +1 -1
- package/dist/tm1-client/services/process-service.js +26 -26
- package/dist/tm1-client/services/security-service.js +8 -9
- package/dist/tm1-client/services/server-service.js +14 -15
- package/dist/tm1-client/services/subset-service.d.ts +23 -10
- package/dist/tm1-client/services/subset-service.js +82 -26
- package/dist/tm1-client/services/view-service.js +12 -13
- package/dist/tm1-client.js +5 -2
- package/dist/tools/analysis/check-v12-readiness.js +3 -3
- package/dist/tools/celldata/check-feeders.js +6 -6
- package/dist/tools/celldata/check-writable-coords.js +33 -22
- package/dist/tools/celldata/execute-mdx.js +10 -9
- package/dist/tools/celldata/get-view.js +10 -9
- package/dist/tools/celldata/member-ref.d.ts +15 -0
- package/dist/tools/celldata/member-ref.js +67 -0
- package/dist/tools/celldata/trace-cell-calculation.js +6 -6
- package/dist/tools/celldata/trace-feeders.js +6 -6
- package/dist/tools/celldata/write-cells.js +62 -12
- package/dist/tools/dimension-management/create-element-attribute.js +1 -1
- package/dist/tools/dimension-management/delete-hierarchy.js +3 -4
- package/dist/tools/dimension-management/get-element-attribute-values.js +7 -2
- package/dist/tools/dimension-management/list-element-attributes.js +1 -1
- package/dist/tools/dimension-management/update-element-attribute-value.js +8 -3
- package/dist/tools/dimension-management/update-element.js +14 -7
- package/dist/tools/fileops/container.d.ts +8 -0
- package/dist/tools/fileops/container.js +11 -0
- package/dist/tools/fileops/delete-file.js +4 -2
- package/dist/tools/fileops/get-file-content.js +45 -7
- package/dist/tools/fileops/list-files.js +5 -2
- package/dist/tools/fileops/search-files.js +4 -1
- package/dist/tools/fileops/upload-file.js +5 -2
- package/dist/tools/index.js +0 -2
- package/dist/tools/model-building/check-cube-rule.js +3 -2
- package/dist/tools/model-building/clear-cube.js +35 -26
- package/dist/tools/model-building/set-cube-rules.js +13 -20
- package/dist/tools/model-building/unload-cube.js +1 -1
- package/dist/tools/operations/get-cube-stats.js +1 -1
- package/dist/tools/operations/get-transaction-log.js +1 -1
- package/dist/tools/operations/list-error-logs.js +27 -2
- package/dist/tools/schemas/items-fileops.d.ts +4 -0
- package/dist/tools/schemas/items-fileops.js +1 -0
- package/dist/tools/security/list-clients.js +5 -5
- package/dist/tools/security/list-groups.js +1 -1
- package/dist/tools/subsets/create-subset.js +9 -5
- package/dist/tools/subsets/delete-subset.js +8 -3
- package/dist/tools/subsets/update-subset.js +9 -9
- package/dist/tools/ti-development/diff-process-with-file.js +4 -29
- package/dist/tools/ti-development/diff-processes.js +9 -0
- package/dist/tools/ti-development/export-process-to-git.js +9 -7
- package/dist/tools/ti-development/export-process-to-pro.js +15 -8
- package/dist/tools/ti-development/import-pro-file.js +10 -21
- package/dist/tools/ti-development/import-process-from-git.js +8 -18
- package/dist/tools/ti-development/install-pro-bundle.js +2 -2
- package/dist/tools/ti-development/upsert-process.js +26 -5
- package/npm-shrinkwrap.json +10 -9
- package/package.json +9 -5
- package/dist/tools/dimension-management/move-element.d.ts +0 -2
- package/dist/tools/dimension-management/move-element.js +0 -28
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { TM1Error } from "../../types.js";
|
|
2
3
|
import { actionResponse } from "../format.js";
|
|
3
4
|
import { CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
|
|
4
5
|
import { DESTRUCTIVE } from "../annotations.js";
|
|
5
6
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
6
7
|
import { defineTool } from "../define-tool.js";
|
|
8
|
+
import { memberRef } from "./member-ref.js";
|
|
7
9
|
import { dimensionCountMismatch } from "../../lib/coordinate-error.js";
|
|
8
10
|
import { resolveCellAddress } from "../../lib/cell-address.js";
|
|
9
11
|
const READ_BACK_LIMIT = 20;
|
|
@@ -17,8 +19,8 @@ export const registerWriteCells = defineTool({
|
|
|
17
19
|
description: [
|
|
18
20
|
"Write one or more cell values directly to a TM1 cube via REST.",
|
|
19
21
|
"IMPORTANT: TI processes are the standard path for data loads — use this tool only for ad-hoc writes or when explicitly requested.",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
+
"Leaf (N-level) coordinates only. Every coordinate is checked before anything is sent and a consolidated one aborts the whole call: writing to a C element is not how TM1 data is loaded, and whether the server would even accept it depends on the account's rights. Aggregate values come from the consolidation, not from a write.",
|
|
23
|
+
"Name every cube dimension, in any order: a dimension left out would land on its default member, so the call is refused. Only Sandboxes may be left out; it is then bound to Base and reported as sandboxDefaulted. A named sandbox is addressable only once its IncludeInSandboxDimension is true.",
|
|
22
24
|
"Before: tm1_check_writable_coords to validate that target coordinates are leaf-level and addressable.",
|
|
23
25
|
"The written cells are read back (up to 20; verified.mismatches lists any that differ, e.g. a rule or spread overriding the value), so no separate tm1_get_cell_value is needed.",
|
|
24
26
|
"Related: tm1_clear_cube for bulk wipe, tm1_execute_process for production data loads.",
|
|
@@ -44,29 +46,77 @@ export const registerWriteCells = defineTool({
|
|
|
44
46
|
.describe("Cells to write"),
|
|
45
47
|
...CONFIRM_SCHEMA,
|
|
46
48
|
},
|
|
47
|
-
handler: async ({ cubeName, dimensions, cells, confirm }, tm1Client) => {
|
|
49
|
+
handler: async ({ cubeName, dimensions: given, cells: givenCells, confirm }, tm1Client) => {
|
|
48
50
|
// Overwrites prior cell values with no undo. Guards against an
|
|
49
51
|
// auto-approve client firing this without intent — NOT a security
|
|
50
52
|
// control: anything that can call the tool can also supply `confirm`.
|
|
51
53
|
requireConfirm(confirm, cubeName, "cube");
|
|
54
|
+
for (const c of givenCells) {
|
|
55
|
+
if (c.elements.length !== given.length) {
|
|
56
|
+
throw dimensionCountMismatch(cubeName, given, c.elements);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Everything below works in cube order over the cube's full dimension
|
|
60
|
+
// list, so a dimension the caller forgot can never reach the MDX tuple.
|
|
61
|
+
const address = resolveCellAddress(cubeName, await tm1Client.cubes.getDimensionNames(cubeName), given);
|
|
62
|
+
const dimensions = address.dimensions;
|
|
63
|
+
const cells = givenCells.map((c) => ({
|
|
64
|
+
...c,
|
|
65
|
+
elements: address.toCubeOrder(c.elements),
|
|
66
|
+
}));
|
|
67
|
+
// One probe per distinct hierarchy, not per cell: a write typically
|
|
68
|
+
// reuses the same handful of elements across many coordinates.
|
|
69
|
+
const byHierarchy = new Map();
|
|
52
70
|
for (const c of cells) {
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
c.elements.forEach((e, idx) => {
|
|
72
|
+
const ref = memberRef(dimensions[idx], e);
|
|
73
|
+
const key = `${ref.dimension}\u0000${ref.hierarchy}`;
|
|
74
|
+
const entry = byHierarchy.get(key) ?? {
|
|
75
|
+
dimension: ref.dimension,
|
|
76
|
+
hierarchy: ref.hierarchy,
|
|
77
|
+
names: new Set(),
|
|
78
|
+
};
|
|
79
|
+
entry.names.add(ref.element);
|
|
80
|
+
byHierarchy.set(key, entry);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const consolidated = [];
|
|
84
|
+
for (const { dimension, hierarchy, names } of byHierarchy.values()) {
|
|
85
|
+
try {
|
|
86
|
+
const hits = await tm1Client.elements.consolidatedAmong(dimension, hierarchy, [...names]);
|
|
87
|
+
for (const hit of hits)
|
|
88
|
+
consolidated.push(`${dimension}:${hit}`);
|
|
55
89
|
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
// Fail closed. A probe that did not answer is not evidence that the
|
|
92
|
+
// coordinates are leaves, and the tool promises every coordinate is
|
|
93
|
+
// checked before anything is sent — so an unanswered probe stops the
|
|
94
|
+
// write instead of reading as "no consolidations found".
|
|
95
|
+
throw new TM1Error({
|
|
96
|
+
code: e instanceof TM1Error ? e.code : "TM1_ERROR",
|
|
97
|
+
message: `Could not check the elements of ${dimension}:${hierarchy} for consolidations: ${e instanceof Error ? e.message : String(e)}. Nothing was sent.`,
|
|
98
|
+
hint: "Verify the dimension and hierarchy names — in `dimensions` and in any qualified element reference — then retry. Use tm1_check_writable_coords to inspect the coordinates first.",
|
|
99
|
+
...(e instanceof TM1Error && e.httpStatus !== undefined
|
|
100
|
+
? { httpStatus: e.httpStatus }
|
|
101
|
+
: {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (consolidated.length > 0) {
|
|
106
|
+
throw new TM1Error({
|
|
107
|
+
code: "VALIDATION_ERROR",
|
|
108
|
+
message: `Consolidated coordinates in this write: ${consolidated.join(", ")}. Nothing was sent.`,
|
|
109
|
+
hint: "Write to N-level (leaf) elements instead — tm1_check_writable_coords reports the level of every coordinate. There is no override: an aggregate is computed from the leaves below it, so load those, or run a TI process via tm1_execute_process.",
|
|
110
|
+
});
|
|
56
111
|
}
|
|
57
112
|
// writeCells throws a TM1Error carrying its own partial-commit accounting
|
|
58
113
|
// (written / failed / notAttempted) + a targeted hint, so we let it
|
|
59
114
|
// propagate to the index.ts Proxy unwrapped — wrapping it here would
|
|
60
115
|
// clobber that hint with a generic one.
|
|
61
|
-
|
|
62
|
-
const ordered = cells.map((c) => ({
|
|
63
|
-
...c,
|
|
64
|
-
elements: address.toCubeOrder(c.elements),
|
|
65
|
-
}));
|
|
66
|
-
await tm1Client.cells.writeCells(cubeName, address.dimensions, ordered);
|
|
116
|
+
await tm1Client.cells.writeCells(cubeName, dimensions, cells);
|
|
67
117
|
// Read back a bounded sample: each read is its own MDX round trip. The
|
|
68
118
|
// write has landed by now, so a failed read-back is reported, not thrown.
|
|
69
|
-
const sample =
|
|
119
|
+
const sample = cells.slice(0, READ_BACK_LIMIT);
|
|
70
120
|
let verified;
|
|
71
121
|
try {
|
|
72
122
|
const readBack = await Promise.all(sample.map((c) => tm1Client.cells.getValue(cubeName, c.elements)));
|
|
@@ -6,7 +6,7 @@ import { defineTool } from "../define-tool.js";
|
|
|
6
6
|
import { HIERARCHY_NAME_OPTIONAL, resolveHierarchy } from "../hierarchy.js";
|
|
7
7
|
export const registerCreateElementAttribute = defineTool({
|
|
8
8
|
name: "tm1_create_element_attribute",
|
|
9
|
-
description: "Create an element attribute definition (schema) on a TM1 hierarchy. For reproducible deployments prefer a TI process (DimensionElementInsert on the }ElementAttributes_{dim} control cube). Use this tool for ad-hoc / debugging scenarios or when explicitly requested.",
|
|
9
|
+
description: "Create an element attribute definition (schema) on a TM1 dimension. Attributes are dimension-wide: created through any hierarchy, the attribute exists on all of them. For reproducible deployments prefer a TI process (DimensionElementInsert on the }ElementAttributes_{dim} control cube). Use this tool for ad-hoc / debugging scenarios or when explicitly requested.",
|
|
10
10
|
annotations: WRITE,
|
|
11
11
|
output: MutationResultSchema,
|
|
12
12
|
input: {
|
|
@@ -5,11 +5,10 @@ import { DESTRUCTIVE } from "../annotations.js";
|
|
|
5
5
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
6
6
|
import { defineTool } from "../define-tool.js";
|
|
7
7
|
import { TM1Error, TM1ErrorCode } from "../../types.js";
|
|
8
|
-
|
|
9
|
-
const norm = (s) => s.toLowerCase().replace(/ /g, "");
|
|
8
|
+
import { tm1NameEquals } from "../../lib/tm1-name.js";
|
|
10
9
|
export const registerDeleteHierarchy = defineTool({
|
|
11
10
|
name: "tm1_delete_hierarchy",
|
|
12
|
-
description: "Delete a hierarchy from a dimension.
|
|
11
|
+
description: "Delete a hierarchy from a dimension. Irreversible — pass confirm=<hierarchy name verbatim>. Deleting the same-named (default) hierarchy is allowed. The tools of this server still default an omitted hierarchyName to the dimension name, so after such a delete pass hierarchyName on every call.",
|
|
13
12
|
annotations: DESTRUCTIVE,
|
|
14
13
|
output: MutationResultSchema,
|
|
15
14
|
input: {
|
|
@@ -21,7 +20,7 @@ export const registerDeleteHierarchy = defineTool({
|
|
|
21
20
|
requireConfirm(confirm, hierarchyName, "hierarchy");
|
|
22
21
|
// TM1 does NOT refuse this itself: 11.8 answers 204 and leaves a dimension
|
|
23
22
|
// listed with no hierarchies at all (verified live). Refuse it here.
|
|
24
|
-
if (
|
|
23
|
+
if (tm1NameEquals(hierarchyName, dimensionName)) {
|
|
25
24
|
throw new TM1Error({
|
|
26
25
|
code: TM1ErrorCode.VALIDATION_ERROR,
|
|
27
26
|
message: `'${hierarchyName}' is the default hierarchy of '${dimensionName}' and cannot be deleted on its own.`,
|
|
@@ -19,6 +19,7 @@ export const registerGetElementAttributeValues = defineTool({
|
|
|
19
19
|
description: [
|
|
20
20
|
"Read attribute values (Numeric/String/Alias) via MDX on the }ElementAttributes_{Dim} control cube. Use this to verify alias values, attribute lookups, or to debug rules referencing ATTRN/ATTRS.",
|
|
21
21
|
"With elementName: that element's attributes. Without: every element of the dimension, name-sorted and paged (items[{elementName, values}]); narrow the columns with attributeNames.",
|
|
22
|
+
"Attribute definitions are dimension-wide. A leaf (N) element has one set of values across all hierarchies, but a consolidated (C) element has its own values per hierarchy, so pass hierarchyName for a consolidation or any element of an alternate hierarchy.",
|
|
22
23
|
],
|
|
23
24
|
annotations: READ_ONLY,
|
|
24
25
|
output: {
|
|
@@ -38,6 +39,10 @@ export const registerGetElementAttributeValues = defineTool({
|
|
|
38
39
|
.string()
|
|
39
40
|
.optional()
|
|
40
41
|
.describe("Element whose attribute values should be read. Omit to page through all elements."),
|
|
42
|
+
hierarchyName: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Hierarchy of the element (default: the same-named one). Single-element mode only."),
|
|
41
46
|
attributeNames: z
|
|
42
47
|
.array(z.string())
|
|
43
48
|
.optional()
|
|
@@ -45,7 +50,7 @@ export const registerGetElementAttributeValues = defineTool({
|
|
|
45
50
|
...PAGINATION_SCHEMA,
|
|
46
51
|
...FORMAT_SCHEMA,
|
|
47
52
|
},
|
|
48
|
-
handler: async ({ dimensionName, elementName, attributeNames, limit, offset, fetchAll, format, }, tm1Client) => {
|
|
53
|
+
handler: async ({ dimensionName, elementName, hierarchyName, attributeNames, limit, offset, fetchAll, format, }, tm1Client) => {
|
|
49
54
|
if (elementName === undefined) {
|
|
50
55
|
const unbounded = fetchAll || limit === 0;
|
|
51
56
|
const start = unbounded ? 0 : offset;
|
|
@@ -65,7 +70,7 @@ export const registerGetElementAttributeValues = defineTool({
|
|
|
65
70
|
]);
|
|
66
71
|
return payloadResponse(payload, format, (p) => `## Attributes of ${p.dimensionName} (${p.offset + 1}–${p.offset + p.count} of ${p.total})\n\n${renderTable(p.items, columns)}`);
|
|
67
72
|
}
|
|
68
|
-
const values = await tm1Client.elements.getAttributeValues(dimensionName, elementName);
|
|
73
|
+
const values = await tm1Client.elements.getAttributeValues(dimensionName, elementName, hierarchyName);
|
|
69
74
|
const payload = { dimensionName, elementName, attributes: values };
|
|
70
75
|
const columns = columnsOf([
|
|
71
76
|
{ header: "attribute", get: (a) => a.attributeName },
|
|
@@ -8,7 +8,7 @@ import { HIERARCHY_NAME_OPTIONAL, resolveHierarchy } from "../hierarchy.js";
|
|
|
8
8
|
import { pageShapeFor } from "../schemas/common.js";
|
|
9
9
|
export const registerListElementAttributes = defineTool({
|
|
10
10
|
name: "tm1_list_element_attributes",
|
|
11
|
-
description: "List element attribute definitions of a TM1
|
|
11
|
+
description: "List element attribute definitions of a TM1 dimension with their types (Numeric/String/Alias); every hierarchy returns the same dimension-wide list. Useful to verify attribute schema before writing values or referencing them in rules (ATTRN/ATTRS).",
|
|
12
12
|
annotations: READ_ONLY,
|
|
13
13
|
output: pageShapeFor(ElementAttributeDefinitionSchema),
|
|
14
14
|
input: {
|
|
@@ -13,6 +13,7 @@ export const registerUpdateElementAttributeValue = defineTool({
|
|
|
13
13
|
description: [
|
|
14
14
|
"Set attribute values on elements by writing to the }ElementAttributes_{Dim} control cube.",
|
|
15
15
|
"One value (elementName + attributeName + value) or many in a single write (updates[]). Values are strings, coerced server-side for Numeric attributes.",
|
|
16
|
+
"Pass hierarchyName for an element of an alternate hierarchy. A leaf's value is shared by every hierarchy of the dimension; a consolidation's value is set for that hierarchy only.",
|
|
16
17
|
"For reproducible deployments prefer a TI process (AttrPutS / AttrPutN); this REST-direct tool is for ad-hoc / debugging scenarios.",
|
|
17
18
|
],
|
|
18
19
|
annotations: IDEMPOTENT_WRITE,
|
|
@@ -23,6 +24,10 @@ export const registerUpdateElementAttributeValue = defineTool({
|
|
|
23
24
|
.string()
|
|
24
25
|
.optional()
|
|
25
26
|
.describe("Element whose attribute value should be set"),
|
|
27
|
+
hierarchyName: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Hierarchy of the element(s) (default: the same-named one)."),
|
|
26
31
|
attributeName: z
|
|
27
32
|
.string()
|
|
28
33
|
.optional()
|
|
@@ -41,7 +46,7 @@ export const registerUpdateElementAttributeValue = defineTool({
|
|
|
41
46
|
.optional()
|
|
42
47
|
.describe("Batch form: many {elementName, attributeName, value} in one cellset write. Replaces elementName/attributeName/value."),
|
|
43
48
|
},
|
|
44
|
-
handler: async ({ dimensionName, elementName, attributeName, value, updates }, tm1Client) => {
|
|
49
|
+
handler: async ({ dimensionName, elementName, hierarchyName, attributeName, value, updates, }, tm1Client) => {
|
|
45
50
|
const single = elementName !== undefined ||
|
|
46
51
|
attributeName !== undefined ||
|
|
47
52
|
value !== undefined;
|
|
@@ -52,7 +57,7 @@ export const registerUpdateElementAttributeValue = defineTool({
|
|
|
52
57
|
});
|
|
53
58
|
}
|
|
54
59
|
if (updates !== undefined) {
|
|
55
|
-
await tm1Client.elements.updateAttributeValues(dimensionName, updates);
|
|
60
|
+
await tm1Client.elements.updateAttributeValues(dimensionName, updates, hierarchyName);
|
|
56
61
|
return actionResponse({
|
|
57
62
|
success: true,
|
|
58
63
|
dimensionName,
|
|
@@ -67,7 +72,7 @@ export const registerUpdateElementAttributeValue = defineTool({
|
|
|
67
72
|
message: "elementName, attributeName and value are all required (or pass updates[]).",
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
|
-
await tm1Client.elements.updateAttributeValue(dimensionName, elementName, attributeName, value);
|
|
75
|
+
await tm1Client.elements.updateAttributeValue(dimensionName, elementName, attributeName, value, hierarchyName);
|
|
71
76
|
return actionResponse({
|
|
72
77
|
success: true,
|
|
73
78
|
dimensionName,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { actionResponse } from "../format.js";
|
|
3
|
-
import {
|
|
3
|
+
import { IDEMPOTENT_DESTRUCTIVE } from "../annotations.js";
|
|
4
4
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
5
5
|
import { defineTool } from "../define-tool.js";
|
|
6
6
|
import { HIERARCHY_NAME_OPTIONAL, resolveHierarchy } from "../hierarchy.js";
|
|
@@ -9,16 +9,16 @@ const updateSchema = z.object({
|
|
|
9
9
|
type: z
|
|
10
10
|
.enum(["Numeric", "String", "Consolidated"])
|
|
11
11
|
.optional()
|
|
12
|
-
.describe("New element type"),
|
|
12
|
+
.describe("New element type, changed in place. Numeric to Consolidated/String discards the element's leaf cell values; the result reports it as typeChange."),
|
|
13
13
|
components: z
|
|
14
14
|
.array(z.object({ name: z.string(), weight: z.number() }))
|
|
15
15
|
.optional()
|
|
16
|
-
.describe("
|
|
16
|
+
.describe("Replaces the element's complete child list: children not listed are removed from this parent (they stay in the dimension with their data). [] removes all children. To move an element between parents, update both parents."),
|
|
17
17
|
});
|
|
18
18
|
export const registerUpdateElement = defineTool({
|
|
19
19
|
name: "tm1_update_element",
|
|
20
|
-
description: "Update an existing element in a TM1 dimension hierarchy (name, type, or components)",
|
|
21
|
-
annotations:
|
|
20
|
+
description: "Update an existing element in a TM1 dimension hierarchy (name, type, or components). components replaces the whole child list, it does not append.",
|
|
21
|
+
annotations: IDEMPOTENT_DESTRUCTIVE,
|
|
22
22
|
output: MutationResultSchema,
|
|
23
23
|
input: {
|
|
24
24
|
dimensionName: z.string().describe("Name of the dimension"),
|
|
@@ -28,8 +28,15 @@ export const registerUpdateElement = defineTool({
|
|
|
28
28
|
},
|
|
29
29
|
handler: async ({ dimensionName, hierarchyName, elementName, update }, tm1Client) => {
|
|
30
30
|
const hierarchy = resolveHierarchy(dimensionName, hierarchyName);
|
|
31
|
-
await tm1Client.elements.update(dimensionName, hierarchy, elementName, update);
|
|
32
|
-
return actionResponse({
|
|
31
|
+
const { typeChange } = await tm1Client.elements.update(dimensionName, hierarchy, elementName, update);
|
|
32
|
+
return actionResponse({
|
|
33
|
+
success: true,
|
|
34
|
+
elementName,
|
|
35
|
+
...(typeChange && {
|
|
36
|
+
typeChange,
|
|
37
|
+
warning: `Type changed in place from ${typeChange.from} to ${typeChange.to}; any existing leaf cell values for this element were discarded.`,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
33
40
|
},
|
|
34
41
|
});
|
|
35
42
|
//# sourceMappingURL=update-element.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Which storage tree the file tools address. Shared by all five so the wording
|
|
3
|
+
// a model reads is identical everywhere.
|
|
4
|
+
export const CONTAINER_SCHEMA = {
|
|
5
|
+
container: z
|
|
6
|
+
.enum(["files", "applications"])
|
|
7
|
+
.optional()
|
|
8
|
+
.default("files")
|
|
9
|
+
.describe("'files' (default): the TI data directory (Files on v12, Blobs on v11). 'applications': the Applications tree of Architect/PAW; nests on both versions, only its documents carry content."),
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -5,6 +5,7 @@ import { actionResponse } from "../format.js";
|
|
|
5
5
|
import { DESTRUCTIVE } from "../annotations.js";
|
|
6
6
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
7
7
|
import { defineTool } from "../define-tool.js";
|
|
8
|
+
import { CONTAINER_SCHEMA } from "./container.js";
|
|
8
9
|
export const registerDeleteFile = defineTool({
|
|
9
10
|
name: "tm1_delete_file",
|
|
10
11
|
description: [
|
|
@@ -21,11 +22,12 @@ export const registerDeleteFile = defineTool({
|
|
|
21
22
|
.string()
|
|
22
23
|
.min(1)
|
|
23
24
|
.describe("File name or path (e.g. 'data.csv' or 'imports/old.csv')."),
|
|
25
|
+
...CONTAINER_SCHEMA,
|
|
24
26
|
...CONFIRM_SCHEMA,
|
|
25
27
|
},
|
|
26
|
-
handler: async ({ fileName, confirm }, tm1Client) => {
|
|
28
|
+
handler: async ({ fileName, container, confirm }, tm1Client) => {
|
|
27
29
|
requireConfirm(confirm, fileName, "file");
|
|
28
|
-
await withToolHint(tm1Client.files.delete(fileName), "Verify exact name with tm1_list_files or tm1_search_files. Names are case-sensitive.");
|
|
30
|
+
await withToolHint(tm1Client.files.delete(fileName, container), "Verify exact name with tm1_list_files or tm1_search_files. Names are case-sensitive.");
|
|
29
31
|
return actionResponse({ success: true, fileName, deleted: true });
|
|
30
32
|
},
|
|
31
33
|
});
|
|
@@ -2,17 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import { FileContentResultSchema } from "../schemas/items.js";
|
|
3
3
|
import { READ_ONLY } from "../annotations.js";
|
|
4
4
|
import { defineTool } from "../define-tool.js";
|
|
5
|
+
import { CONTAINER_SCHEMA } from "./container.js";
|
|
5
6
|
// Below the server's 80k-character response limit (TM1_MAX_RESPONSE_CHARS)
|
|
6
7
|
// once JSON escaping is added; the old 256 KB default could never be returned.
|
|
7
8
|
const DEFAULT_MAX_BYTES = 64 * 1024;
|
|
9
|
+
// base64 grows the body by 4/3: 48 KB comes out at 64k characters, where the
|
|
10
|
+
// text default would come out at ~87k and be refused by the size guard.
|
|
11
|
+
const DEFAULT_BASE64_MAX_BYTES = 48 * 1024;
|
|
8
12
|
const HARD_MAX_BYTES = 4 * 1024 * 1024;
|
|
9
13
|
export const registerGetFileContent = defineTool({
|
|
10
14
|
name: "tm1_get_file_content",
|
|
11
15
|
description: [
|
|
12
16
|
"Read the content of a file from the TM1 server's data directory.",
|
|
13
17
|
"Use to inspect CSV, TXT, or other text files before building import processes.",
|
|
14
|
-
"Auto-falls back from v12 (Files) to v11 (Blobs) container.",
|
|
15
|
-
"
|
|
18
|
+
"Auto-falls back from v12 (Files) to v11 (Blobs) container. Set container='applications' to read a document out of the Applications tree; a folder or a view reference there carries no content and is refused by name.",
|
|
19
|
+
"encoding='base64' returns the bytes untouched — use it for spreadsheets and other binaries, which a text read would corrupt.",
|
|
20
|
+
"Response is truncated to maxBytes (default 64 KB, 48 KB for base64) to keep MCP messages small.",
|
|
16
21
|
],
|
|
17
22
|
annotations: READ_ONLY,
|
|
18
23
|
output: FileContentResultSchema,
|
|
@@ -26,8 +31,13 @@ export const registerGetFileContent = defineTool({
|
|
|
26
31
|
.positive()
|
|
27
32
|
.max(HARD_MAX_BYTES)
|
|
28
33
|
.optional()
|
|
29
|
-
.default
|
|
30
|
-
|
|
34
|
+
.describe(`Truncate response after N bytes (default ${DEFAULT_MAX_BYTES}, ${DEFAULT_BASE64_MAX_BYTES} for base64; hard max ${HARD_MAX_BYTES}).`),
|
|
35
|
+
...CONTAINER_SCHEMA,
|
|
36
|
+
encoding: z
|
|
37
|
+
.enum(["text", "base64"])
|
|
38
|
+
.optional()
|
|
39
|
+
.default("text")
|
|
40
|
+
.describe("How to return the body. 'text' (default) decodes as UTF-8; 'base64' hands back the raw bytes. headLines only applies to text."),
|
|
31
41
|
headLines: z
|
|
32
42
|
.number()
|
|
33
43
|
.int()
|
|
@@ -36,12 +46,39 @@ export const registerGetFileContent = defineTool({
|
|
|
36
46
|
.optional()
|
|
37
47
|
.describe("If set, only return the first N lines (overrides byte truncation)."),
|
|
38
48
|
},
|
|
39
|
-
handler: async ({ fileName, maxBytes, headLines }, tm1Client) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
49
|
+
handler: async ({ fileName, maxBytes: requestedMaxBytes, headLines, container, encoding }, tm1Client) => {
|
|
50
|
+
const maxBytes = requestedMaxBytes ??
|
|
51
|
+
(encoding === "base64" ? DEFAULT_BASE64_MAX_BYTES : DEFAULT_MAX_BYTES);
|
|
52
|
+
const bytes = await tm1Client.files.getContentBytes(fileName, container);
|
|
53
|
+
const totalBytes = bytes.byteLength;
|
|
42
54
|
let body;
|
|
43
55
|
let truncated = false;
|
|
44
56
|
let truncationReason;
|
|
57
|
+
if (encoding === "base64") {
|
|
58
|
+
const slice = bytes.subarray(0, maxBytes);
|
|
59
|
+
body = slice.toString("base64");
|
|
60
|
+
if (slice.byteLength < totalBytes) {
|
|
61
|
+
truncated = true;
|
|
62
|
+
truncationReason = `maxBytes=${maxBytes}`;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: JSON.stringify({
|
|
69
|
+
fileName,
|
|
70
|
+
totalBytes,
|
|
71
|
+
returnedBytes: slice.byteLength,
|
|
72
|
+
truncated,
|
|
73
|
+
...(truncationReason ? { truncationReason } : {}),
|
|
74
|
+
encoding,
|
|
75
|
+
content: body,
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const content = bytes.toString("utf8");
|
|
45
82
|
if (headLines !== undefined) {
|
|
46
83
|
const allLines = content.split("\n");
|
|
47
84
|
body = allLines.slice(0, headLines).join("\n");
|
|
@@ -66,6 +103,7 @@ export const registerGetFileContent = defineTool({
|
|
|
66
103
|
returnedBytes: Buffer.byteLength(body, "utf8"),
|
|
67
104
|
truncated,
|
|
68
105
|
...(truncationReason ? { truncationReason } : {}),
|
|
106
|
+
encoding,
|
|
69
107
|
content: body,
|
|
70
108
|
};
|
|
71
109
|
return {
|
|
@@ -3,6 +3,7 @@ import { PAGINATION_SCHEMA, paginate } from "../pagination.js";
|
|
|
3
3
|
import { FORMAT_SCHEMA, wrappedPageResponse, columnsOf } from "../format.js";
|
|
4
4
|
import { READ_ONLY } from "../annotations.js";
|
|
5
5
|
import { defineTool } from "../define-tool.js";
|
|
6
|
+
import { CONTAINER_SCHEMA } from "./container.js";
|
|
6
7
|
import { FilenameItemSchema } from "../schemas/items.js";
|
|
7
8
|
import { pageShapeFor } from "../schemas/common.js";
|
|
8
9
|
// tm1_list_files prefixes a `path` field on top of the page envelope.
|
|
@@ -17,6 +18,7 @@ export const registerListFiles = defineTool({
|
|
|
17
18
|
"Use to browse available CSV, TXT, or other files before building import processes.",
|
|
18
19
|
"Supports subfolder navigation via the path parameter.",
|
|
19
20
|
"Auto-falls back from v12 (Files) to v11 (Blobs) container.",
|
|
21
|
+
"Set container='applications' to browse the Applications tree instead.",
|
|
20
22
|
"Paginated (default 50/page).",
|
|
21
23
|
],
|
|
22
24
|
annotations: READ_ONLY,
|
|
@@ -26,11 +28,12 @@ export const registerListFiles = defineTool({
|
|
|
26
28
|
.string()
|
|
27
29
|
.optional()
|
|
28
30
|
.describe("Subfolder path (e.g. 'imports' or 'imports/2024'). Empty = root."),
|
|
31
|
+
...CONTAINER_SCHEMA,
|
|
29
32
|
...PAGINATION_SCHEMA,
|
|
30
33
|
...FORMAT_SCHEMA,
|
|
31
34
|
},
|
|
32
|
-
handler: async ({ path, limit, offset, fetchAll, format }, tm1Client) => {
|
|
33
|
-
const files = await tm1Client.files.list(path);
|
|
35
|
+
handler: async ({ path, container, limit, offset, fetchAll, format }, tm1Client) => {
|
|
36
|
+
const files = await tm1Client.files.list(path, container);
|
|
34
37
|
const page = paginate(files, limit, offset, fetchAll);
|
|
35
38
|
const wrapper = { path: path ?? "", ...page };
|
|
36
39
|
const columns = columnsOf([{ header: "filename", get: (f) => f }]);
|
|
@@ -3,6 +3,7 @@ import { PAGINATION_SCHEMA, paginate } from "../pagination.js";
|
|
|
3
3
|
import { FORMAT_SCHEMA, wrappedPageResponse, columnsOf } from "../format.js";
|
|
4
4
|
import { READ_ONLY } from "../annotations.js";
|
|
5
5
|
import { defineTool } from "../define-tool.js";
|
|
6
|
+
import { CONTAINER_SCHEMA } from "./container.js";
|
|
6
7
|
import { FilenameItemSchema } from "../schemas/items.js";
|
|
7
8
|
import { pageShapeFor } from "../schemas/common.js";
|
|
8
9
|
// tm1_search_files echoes the search filters alongside the page envelope so
|
|
@@ -48,15 +49,17 @@ export const registerSearchFiles = defineTool({
|
|
|
48
49
|
.string()
|
|
49
50
|
.optional()
|
|
50
51
|
.describe("Subfolder to search in (v12 only). Empty = root."),
|
|
52
|
+
...CONTAINER_SCHEMA,
|
|
51
53
|
...PAGINATION_SCHEMA,
|
|
52
54
|
...FORMAT_SCHEMA,
|
|
53
55
|
},
|
|
54
|
-
handler: async ({ startswith, contains, operator, path, limit, offset, fetchAll, format }, tm1Client) => {
|
|
56
|
+
handler: async ({ startswith, contains, operator, path, container, limit, offset, fetchAll, format, }, tm1Client) => {
|
|
55
57
|
const names = await tm1Client.files.search({
|
|
56
58
|
startswith,
|
|
57
59
|
contains,
|
|
58
60
|
operator,
|
|
59
61
|
path,
|
|
62
|
+
container,
|
|
60
63
|
});
|
|
61
64
|
const page = paginate(names, limit, offset, fetchAll);
|
|
62
65
|
const wrapper = {
|
|
@@ -5,6 +5,7 @@ import { CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
|
|
|
5
5
|
import { IDEMPOTENT_WRITE } from "../annotations.js";
|
|
6
6
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
7
7
|
import { defineTool } from "../define-tool.js";
|
|
8
|
+
import { CONTAINER_SCHEMA } from "./container.js";
|
|
8
9
|
const HARD_MAX_BYTES = 32 * 1024 * 1024;
|
|
9
10
|
export const registerUploadFile = defineTool({
|
|
10
11
|
name: "tm1_upload_file",
|
|
@@ -14,6 +15,7 @@ export const registerUploadFile = defineTool({
|
|
|
14
15
|
"Provide content as plain text OR base64 (set encoding='base64' for binary).",
|
|
15
16
|
"Auto-falls back from v12 (Files) to v11 (Blobs) container.",
|
|
16
17
|
"v11: subfolders not supported — use a flat file name. v12: nested paths OK if folders exist.",
|
|
18
|
+
"container='applications' writes into the Applications tree instead, which nests on both versions. The parent folder must already exist there too.",
|
|
17
19
|
`Hard max size: ${HARD_MAX_BYTES} bytes (32 MB).`,
|
|
18
20
|
],
|
|
19
21
|
annotations: IDEMPOTENT_WRITE,
|
|
@@ -31,9 +33,10 @@ export const registerUploadFile = defineTool({
|
|
|
31
33
|
.optional()
|
|
32
34
|
.default("text")
|
|
33
35
|
.describe("Encoding of the `content` field. 'text' (default) for UTF-8 text, 'base64' for binary."),
|
|
36
|
+
...CONTAINER_SCHEMA,
|
|
34
37
|
...CONFIRM_SCHEMA,
|
|
35
38
|
},
|
|
36
|
-
handler: async ({ fileName, content, encoding, confirm }, tm1Client) => {
|
|
39
|
+
handler: async ({ fileName, content, encoding, container, confirm }, tm1Client) => {
|
|
37
40
|
// Silently replaces an existing server file of the same name. Guards
|
|
38
41
|
// against accidental invocation — not a security control.
|
|
39
42
|
requireConfirm(confirm, fileName, "file");
|
|
@@ -55,7 +58,7 @@ export const registerUploadFile = defineTool({
|
|
|
55
58
|
],
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
|
-
const result = await withToolHint(tm1Client.files.upload(fileName, bytes), "If parent folder is missing, create it on TM1 v12 before retrying. v11 supports root only.");
|
|
61
|
+
const result = await withToolHint(tm1Client.files.upload(fileName, bytes, container), "If parent folder is missing, create it on TM1 v12 before retrying. v11 supports root only.");
|
|
59
62
|
return actionResponse({
|
|
60
63
|
success: true,
|
|
61
64
|
fileName,
|
package/dist/tools/index.js
CHANGED
|
@@ -42,7 +42,6 @@ import { registerCreateElement } from "./dimension-management/create-element.js"
|
|
|
42
42
|
import { registerUpdateElement } from "./dimension-management/update-element.js";
|
|
43
43
|
import { registerDeleteElement } from "./dimension-management/delete-element.js";
|
|
44
44
|
import { registerDeleteElements } from "./dimension-management/delete-elements.js";
|
|
45
|
-
import { registerMoveElement } from "./dimension-management/move-element.js";
|
|
46
45
|
import { registerCreateDimension } from "./dimension-management/create-dimension.js";
|
|
47
46
|
import { registerDeleteDimension } from "./dimension-management/delete-dimension.js";
|
|
48
47
|
import { registerBulkUpsertElements } from "./dimension-management/bulk-upsert-elements.js";
|
|
@@ -174,7 +173,6 @@ const REGISTRARS = [
|
|
|
174
173
|
registerUpdateElement,
|
|
175
174
|
registerDeleteElement,
|
|
176
175
|
registerDeleteElements,
|
|
177
|
-
registerMoveElement,
|
|
178
176
|
registerListElementAttributes,
|
|
179
177
|
registerCreateElementAttribute,
|
|
180
178
|
registerGetElementAttributeValues,
|
|
@@ -6,7 +6,7 @@ export const registerCheckCubeRule = defineTool({
|
|
|
6
6
|
name: "tm1_check_cube_rule",
|
|
7
7
|
description: [
|
|
8
8
|
"Validate the syntax of a TM1 cube rule WITHOUT applying it.",
|
|
9
|
-
"Returns
|
|
9
|
+
"Returns ok:true, or ok:false with the syntax errors and their line numbers; a rule with errors is a normal result, not a tool error.",
|
|
10
10
|
"Takes the same sources as tm1_set_cube_rules — rules, edits or filePath — and checks the full resulting text, so an edits patch is validated exactly as it would be installed. tm1_set_cube_rules runs this check itself before writing.",
|
|
11
11
|
],
|
|
12
12
|
annotations: READ_ONLY,
|
|
@@ -42,8 +42,9 @@ export const registerCheckCubeRule = defineTool({
|
|
|
42
42
|
message: e.message,
|
|
43
43
|
})),
|
|
44
44
|
};
|
|
45
|
+
// A rule with syntax errors is the answer to the question, not a failed
|
|
46
|
+
// call: flagging it isError would drop structuredContent for the payload.
|
|
45
47
|
return {
|
|
46
|
-
isError: !ok || undefined,
|
|
47
48
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
48
49
|
};
|
|
49
50
|
},
|