@ffschrattenecker/tm1-mcp-server 6.0.0 → 7.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 +110 -1
- package/README.md +28 -7
- package/dist/config.d.ts +12 -1
- package/dist/config.js +120 -79
- package/dist/connections.d.ts +69 -0
- package/dist/connections.js +262 -0
- package/dist/http-transport.d.ts +2 -2
- package/dist/index.js +45 -37
- package/dist/lib/callgraph/tm1-adapter.d.ts +4 -1
- package/dist/lib/callgraph/tm1-adapter.js +5 -2
- package/dist/lib/slim-json-schema.d.ts +8 -0
- package/dist/lib/slim-json-schema.js +34 -0
- package/dist/lib/strip-comments.js +1 -1
- package/dist/lib/tm1-events.d.ts +2 -0
- package/dist/prompts/index.js +2 -2
- package/dist/resources/index.d.ts +4 -2
- package/dist/resources/index.js +93 -53
- package/dist/resources/subscriptions.d.ts +2 -1
- package/dist/resources/subscriptions.js +16 -7
- package/dist/server-instructions.d.ts +3 -0
- package/dist/server-instructions.js +12 -0
- package/dist/tm1-client/http.d.ts +1 -1
- package/dist/tm1-client/http.js +21 -4
- package/dist/tm1-client/services/process-service.d.ts +7 -1
- package/dist/tm1-client/services/process-service.js +7 -5
- package/dist/tm1-client.d.ts +7 -1
- package/dist/tm1-client.js +10 -11
- package/dist/tools/analysis/analyze-object-usage.d.ts +10 -0
- package/dist/tools/analysis/analyze-object-usage.js +37 -33
- package/dist/tools/analysis/audit-naming.js +1 -1
- package/dist/tools/analysis/delete-impact.d.ts +13 -0
- package/dist/tools/analysis/delete-impact.js +30 -0
- package/dist/tools/analysis/invalidate-callgraph-cache.js +2 -1
- package/dist/tools/analysis/trace-data-flow.js +1 -1
- package/dist/tools/celldata/write-cells.js +37 -2
- package/dist/tools/confirm.d.ts +4 -1
- package/dist/tools/confirm.js +12 -2
- package/dist/tools/define-tool.d.ts +35 -11
- package/dist/tools/define-tool.js +51 -4
- package/dist/tools/dimension-management/delete-dimension.js +18 -4
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +6 -12
- package/dist/tools/metadata/list-cubes.js +3 -3
- package/dist/tools/metadata/list-processes.js +11 -7
- package/dist/tools/model-building/delete-cube.js +18 -4
- package/dist/tools/model-building/set-cube-rules.js +13 -1
- package/dist/tools/model-building/unload-cube.js +1 -1
- package/dist/tools/operations/get-audit-log.js +1 -1
- package/dist/tools/operations/get-jobs.js +2 -2
- package/dist/tools/operations/get-message-log.js +1 -1
- package/dist/tools/operations/get-server-info.js +10 -1
- package/dist/tools/operations/get-server-state.js +1 -1
- package/dist/tools/operations/get-threads.js +2 -2
- package/dist/tools/operations/get-transaction-log.js +1 -1
- package/dist/tools/operations/list-connections.d.ts +2 -0
- package/dist/tools/operations/list-connections.js +35 -0
- package/dist/tools/operations/save-data.js +1 -1
- package/dist/tools/schemas/items-monitoring.d.ts +11 -0
- package/dist/tools/schemas/items-monitoring.js +9 -1
- package/dist/tools/schemas/items-processes.d.ts +3 -0
- package/dist/tools/schemas/items-processes.js +2 -1
- package/dist/tools/ti-development/diff-processes.d.ts +42 -0
- package/dist/tools/ti-development/diff-processes.js +4 -4
- package/dist/tools/ti-development/execute-process.js +29 -4
- package/dist/tools/ti-development/import-pro-file.js +1 -1
- package/dist/tools/ti-development/preflight.d.ts +24 -0
- package/dist/tools/ti-development/preflight.js +32 -0
- package/dist/tools/ti-development/search-code.js +1 -1
- package/dist/tools/ti-development/upsert-process.js +100 -11
- package/dist/tools/with-annotations.d.ts +10 -0
- package/dist/tools/with-annotations.js +58 -3
- package/npm-shrinkwrap.json +4586 -0
- package/package.json +9 -7
- package/dist/tools/ti-development/get-process-code.d.ts +0 -2
- package/dist/tools/ti-development/get-process-code.js +0 -100
- package/dist/tools/ti-development/get-process-datasource.d.ts +0 -2
- package/dist/tools/ti-development/get-process-datasource.js +0 -20
- package/dist/tools/ti-development/get-process-parameters.d.ts +0 -2
- package/dist/tools/ti-development/get-process-parameters.js +0 -25
- package/dist/tools/ti-development/get-process-variables.d.ts +0 -2
- package/dist/tools/ti-development/get-process-variables.js +0 -38
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TM1Client } from "../../tm1-client.js";
|
|
2
|
+
export declare function deleteImpact(tm1Client: TM1Client, kind: "cube" | "dimension", name: string): Promise<{
|
|
3
|
+
sources: {
|
|
4
|
+
sourceKind: "process" | "rule";
|
|
5
|
+
sourceName: string;
|
|
6
|
+
accessTypes: string[];
|
|
7
|
+
count: number;
|
|
8
|
+
}[];
|
|
9
|
+
truncated?: boolean;
|
|
10
|
+
referencingSources: number;
|
|
11
|
+
usedInCubes?: string[];
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=delete-impact.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { buildIndexFromTM1 } from "../../lib/callgraph/tm1-adapter.js";
|
|
2
|
+
import { buildCubeOrDimUsages } from "../../lib/callgraph/callGraph.js";
|
|
3
|
+
import { summarizeBySource } from "./analyze-object-usage.js";
|
|
4
|
+
const MAX_SOURCES = 50;
|
|
5
|
+
export async function deleteImpact(tm1Client, kind, name) {
|
|
6
|
+
const [index, cubes] = await Promise.all([
|
|
7
|
+
buildIndexFromTM1(tm1Client, { includeControl: false }),
|
|
8
|
+
kind === "dimension" ? tm1Client.cubes.list() : Promise.resolve([]),
|
|
9
|
+
]);
|
|
10
|
+
const sources = summarizeBySource(buildCubeOrDimUsages(index, kind, name, {
|
|
11
|
+
includeSystem: false,
|
|
12
|
+
accessMode: "all",
|
|
13
|
+
}));
|
|
14
|
+
const key = name.toLowerCase();
|
|
15
|
+
const usedInCubes = cubes
|
|
16
|
+
.filter((c) => (c.dimensions ?? []).some((d) => d.toLowerCase() === key))
|
|
17
|
+
.map((c) => c.name);
|
|
18
|
+
return {
|
|
19
|
+
...(kind === "dimension" ? { usedInCubes } : {}),
|
|
20
|
+
referencingSources: sources.length,
|
|
21
|
+
...(sources.length > MAX_SOURCES ? { truncated: true } : {}),
|
|
22
|
+
sources: sources.slice(0, MAX_SOURCES).map((s) => ({
|
|
23
|
+
sourceKind: s.sourceKind,
|
|
24
|
+
sourceName: s.sourceName,
|
|
25
|
+
accessTypes: s.accessTypes,
|
|
26
|
+
count: s.count,
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=delete-impact.js.map
|
|
@@ -7,8 +7,9 @@ export const registerInvalidateCallgraphCache = defineTool({
|
|
|
7
7
|
description: "Drop the in-memory ReferenceIndex cache used by tm1_analyze_callgraph / tm1_analyze_object_usage / tm1_analyze_chore_graph. Rarely needed: every successful mutating call through this server already drops it. Use it only after changes made outside this server (Workspace, TI, another client). The next analysis call rebuilds the index (single bulk fetch). Touches no TM1 object, so it is available in readonly mode.",
|
|
8
8
|
annotations: READ_ONLY,
|
|
9
9
|
output: InvalidateCallgraphCacheResultSchema,
|
|
10
|
-
// Takes no arguments: the cache is process-wide.
|
|
10
|
+
// Takes no arguments and no connection: the cache is process-wide.
|
|
11
11
|
input: {},
|
|
12
|
+
connectionless: true,
|
|
12
13
|
handler: () => {
|
|
13
14
|
try {
|
|
14
15
|
const before = getCallgraphCacheStats();
|
|
@@ -13,7 +13,7 @@ const FLOW_MAX_ITEMS = 500;
|
|
|
13
13
|
export const registerTraceDataFlow = defineTool({
|
|
14
14
|
name: "tm1_trace_data_flow",
|
|
15
15
|
description: [
|
|
16
|
-
"Trace data flow into and out of a cube in one call, instead of analyze_object_usage + N×
|
|
16
|
+
"Trace data flow into and out of a cube in one call, instead of analyze_object_usage + N× get_process.",
|
|
17
17
|
"downstream: processes that READ the cube and which cubes they WRITE to (where the data flows next).",
|
|
18
18
|
"upstream: processes that WRITE the cube and where they SOURCE data (read cubes + datasource).",
|
|
19
19
|
"Combines code-level CellGet/CellPut/DB access with each process's datasource, so view-sourced reads",
|
|
@@ -6,6 +6,12 @@ import { MutationResultSchema } from "../schemas/items.js";
|
|
|
6
6
|
import { defineTool } from "../define-tool.js";
|
|
7
7
|
import { dimensionCountMismatch } from "../../lib/coordinate-error.js";
|
|
8
8
|
import { resolveCellAddress } from "../../lib/cell-address.js";
|
|
9
|
+
const READ_BACK_LIMIT = 20;
|
|
10
|
+
function sameValue(sent, stored) {
|
|
11
|
+
if (typeof sent === "number")
|
|
12
|
+
return Number(stored) === sent;
|
|
13
|
+
return String(stored ?? "") === sent;
|
|
14
|
+
}
|
|
9
15
|
export const registerWriteCells = defineTool({
|
|
10
16
|
name: "tm1_write_cells",
|
|
11
17
|
description: [
|
|
@@ -14,7 +20,8 @@ export const registerWriteCells = defineTool({
|
|
|
14
20
|
"Writes to consolidated cells are rejected by TM1.",
|
|
15
21
|
"Every cube dimension must be named (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 bound to Base and echoed as sandboxDefaulted. A named sandbox is addressable only once its IncludeInSandboxDimension is true.",
|
|
16
22
|
"Before: tm1_check_writable_coords to validate that target coordinates are leaf-level and addressable.",
|
|
17
|
-
"
|
|
23
|
+
"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
|
+
"Related: tm1_clear_cube for bulk wipe, tm1_execute_process for production data loads.",
|
|
18
25
|
],
|
|
19
26
|
annotations: DESTRUCTIVE,
|
|
20
27
|
output: MutationResultSchema,
|
|
@@ -52,10 +59,38 @@ export const registerWriteCells = defineTool({
|
|
|
52
59
|
// propagate to the index.ts Proxy unwrapped — wrapping it here would
|
|
53
60
|
// clobber that hint with a generic one.
|
|
54
61
|
const address = resolveCellAddress(cubeName, await tm1Client.cubes.getDimensionNames(cubeName), dimensions);
|
|
55
|
-
|
|
62
|
+
const ordered = cells.map((c) => ({
|
|
63
|
+
...c,
|
|
64
|
+
elements: address.toCubeOrder(c.elements),
|
|
65
|
+
}));
|
|
66
|
+
await tm1Client.cells.writeCells(cubeName, address.dimensions, ordered);
|
|
67
|
+
// Read back a bounded sample: each read is its own MDX round trip. The
|
|
68
|
+
// write has landed by now, so a failed read-back is reported, not thrown.
|
|
69
|
+
const sample = ordered.slice(0, READ_BACK_LIMIT);
|
|
70
|
+
let verified;
|
|
71
|
+
try {
|
|
72
|
+
const readBack = await Promise.all(sample.map((c) => tm1Client.cells.getValue(cubeName, c.elements)));
|
|
73
|
+
verified = {
|
|
74
|
+
checked: sample.length,
|
|
75
|
+
...(cells.length > sample.length
|
|
76
|
+
? { unchecked: cells.length - sample.length }
|
|
77
|
+
: {}),
|
|
78
|
+
mismatches: sample
|
|
79
|
+
.map((c, i) => ({
|
|
80
|
+
elements: c.elements,
|
|
81
|
+
sent: c.value,
|
|
82
|
+
stored: readBack[i],
|
|
83
|
+
}))
|
|
84
|
+
.filter((m) => !sameValue(m.sent, m.stored)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
verified = { checked: 0, readBackError: e.message };
|
|
89
|
+
}
|
|
56
90
|
return actionResponse({
|
|
57
91
|
success: true,
|
|
58
92
|
cellsWritten: cells.length,
|
|
93
|
+
verified,
|
|
59
94
|
...(address.sandboxDefaulted
|
|
60
95
|
? { sandboxDefaulted: address.sandboxDefaulted }
|
|
61
96
|
: {}),
|
package/dist/tools/confirm.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const CONFIRM_SCHEMA: {
|
|
3
3
|
confirm: z.ZodString;
|
|
4
4
|
};
|
|
5
|
-
export declare
|
|
5
|
+
export declare const DRY_RUN_CONFIRM_SCHEMA: {
|
|
6
|
+
confirm: z.ZodOptional<z.ZodString>;
|
|
7
|
+
};
|
|
8
|
+
export declare function requireConfirm(provided: string | undefined, target: string, kind: string): void;
|
|
6
9
|
export declare const OVERWRITE_CONFIRM_SCHEMA: {
|
|
7
10
|
confirm: z.ZodOptional<z.ZodString>;
|
|
8
11
|
};
|
package/dist/tools/confirm.js
CHANGED
|
@@ -27,13 +27,23 @@ import { TM1Error, TM1ErrorCode } from "../types.js";
|
|
|
27
27
|
export const CONFIRM_SCHEMA = {
|
|
28
28
|
confirm: z
|
|
29
29
|
.string()
|
|
30
|
-
.describe("
|
|
30
|
+
.describe("Repeat the target name verbatim to confirm this irreversible action; a mismatch rejects the call."),
|
|
31
|
+
};
|
|
32
|
+
// For a destructive tool with a dryRun: a dry run needs no confirm, so the
|
|
33
|
+
// field is optional in the schema and required at runtime on the real call.
|
|
34
|
+
export const DRY_RUN_CONFIRM_SCHEMA = {
|
|
35
|
+
confirm: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Required unless dryRun=true: repeat the target identifier verbatim to confirm this irreversible action. A dry run is not a confirmation."),
|
|
31
39
|
};
|
|
32
40
|
export function requireConfirm(provided, target, kind) {
|
|
33
41
|
if (provided !== target) {
|
|
34
42
|
throw new TM1Error({
|
|
35
43
|
code: TM1ErrorCode.VALIDATION_ERROR,
|
|
36
|
-
message:
|
|
44
|
+
message: provided === undefined
|
|
45
|
+
? `This call is irreversible for ${kind} "${target}" and needs confirm="${target}". Nothing was changed.`
|
|
46
|
+
: `confirm mismatch — expected ${kind} name "${target}", got "${provided}".`,
|
|
37
47
|
hint: `Re-issue the call with confirm="${target}" verbatim. This safety check prevents accidental destructive operations.`,
|
|
38
48
|
});
|
|
39
49
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { McpServer, ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type ZodObject, type ZodRawShape, type ZodTypeAny } from "zod";
|
|
3
3
|
import type { TM1Client } from "../tm1-client.js";
|
|
4
|
+
import { ConnectionRegistry } from "../connections.js";
|
|
4
5
|
import type { Tm1ToolAnnotations } from "./annotations.js";
|
|
6
|
+
/**
|
|
7
|
+
* Where a tool gets its client from: the multi-connection registry in
|
|
8
|
+
* production, or one prebuilt client (unit tests, embedders), which is wrapped
|
|
9
|
+
* as a single-connection registry.
|
|
10
|
+
*/
|
|
11
|
+
export type ClientSource = TM1Client | ConnectionRegistry;
|
|
5
12
|
/** Registrar shape expected by the REGISTRARS array in ./index.ts. */
|
|
6
|
-
export type ToolRegistrar = (server: McpServer,
|
|
7
|
-
export
|
|
13
|
+
export type ToolRegistrar = (server: McpServer, source: ClientSource) => void;
|
|
14
|
+
export declare function asRegistry(source: ClientSource): ConnectionRegistry;
|
|
15
|
+
interface ToolSpecBase<I extends ZodRawShape> {
|
|
8
16
|
/** Wire name, `tm1_*`. */
|
|
9
17
|
name: string;
|
|
10
18
|
/**
|
|
@@ -24,19 +32,34 @@ export interface ToolSpec<I extends ZodRawShape> {
|
|
|
24
32
|
/** MCP behavior hints — one of the presets in ./annotations.js. */
|
|
25
33
|
annotations: Tm1ToolAnnotations;
|
|
26
34
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* Gate for tools that exist on one TM1 generation only (v11 threads vs v12
|
|
36
|
+
* jobs, v11-only save_data). The tool is registered only when at least one
|
|
37
|
+
* connection runs that version, and a call against a connection on the other
|
|
38
|
+
* version is refused. This is deliberately separate from the
|
|
39
|
+
* `requiresVersion` annotation, which is a client-facing HINT and is also
|
|
40
|
+
* carried by tools that stay registered on both generations.
|
|
32
41
|
*/
|
|
33
|
-
|
|
42
|
+
version?: 11 | 12;
|
|
43
|
+
}
|
|
44
|
+
/** A tool that acts on one TM1 connection — every tool but a handful. */
|
|
45
|
+
interface ConnectionToolSpec<I extends ZodRawShape> extends ToolSpecBase<I> {
|
|
46
|
+
connectionless?: false;
|
|
34
47
|
/**
|
|
35
|
-
* Handler. Receives the parsed args, the
|
|
36
|
-
* per-call extra (abort signal, request metadata).
|
|
48
|
+
* Handler. Receives the parsed args, the client of the connection the call
|
|
49
|
+
* targets, and the SDK's per-call extra (abort signal, request metadata).
|
|
37
50
|
*/
|
|
38
51
|
handler: (args: Parameters<ToolCallback<I>>[0], tm1Client: TM1Client, extra: Parameters<ToolCallback<I>>[1]) => ReturnType<ToolCallback<I>>;
|
|
39
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A tool about the connections themselves (tm1_list_connections): it takes no
|
|
55
|
+
* `connection` argument and gets the registry instead of a client, so calling
|
|
56
|
+
* it never logs in anywhere.
|
|
57
|
+
*/
|
|
58
|
+
interface ConnectionlessToolSpec<I extends ZodRawShape> extends ToolSpecBase<I> {
|
|
59
|
+
connectionless: true;
|
|
60
|
+
handler: (args: Parameters<ToolCallback<I>>[0], registry: ConnectionRegistry, extra: Parameters<ToolCallback<I>>[1]) => ReturnType<ToolCallback<I>>;
|
|
61
|
+
}
|
|
62
|
+
export type ToolSpec<I extends ZodRawShape> = ConnectionToolSpec<I> | ConnectionlessToolSpec<I>;
|
|
40
63
|
/** What ./with-annotations.ts needs at registration time. */
|
|
41
64
|
export interface ResolvedSpec {
|
|
42
65
|
name: string;
|
|
@@ -48,4 +71,5 @@ export declare function specFor(name: string): ResolvedSpec | undefined;
|
|
|
48
71
|
/** Every spec defined so far. Used by the lint gates and by tests. */
|
|
49
72
|
export declare function allSpecs(): ReadonlyMap<string, ResolvedSpec>;
|
|
50
73
|
export declare function defineTool<I extends ZodRawShape>(spec: ToolSpec<I>): ToolRegistrar;
|
|
74
|
+
export {};
|
|
51
75
|
//# sourceMappingURL=define-tool.d.ts.map
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ConnectionRegistry } from "../connections.js";
|
|
3
|
+
import { TM1Error, TM1ErrorCode } from "../types.js";
|
|
1
4
|
import { asOutputSchema } from "./schemas/output-schema.js";
|
|
2
5
|
import { markdownCapable } from "./schemas/markdown-capable.js";
|
|
6
|
+
export function asRegistry(source) {
|
|
7
|
+
return source instanceof ConnectionRegistry
|
|
8
|
+
? source
|
|
9
|
+
: ConnectionRegistry.single(source);
|
|
10
|
+
}
|
|
3
11
|
// Populated at module-load time, when a tool file's top-level defineTool()
|
|
4
12
|
// call runs. That is strictly before registerAllTools() executes, because the
|
|
5
13
|
// import graph resolves first — so the Proxy always finds the spec.
|
|
@@ -36,14 +44,53 @@ export function defineTool(spec) {
|
|
|
36
44
|
// `outputSchema: undefined` for an optional property.
|
|
37
45
|
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
38
46
|
});
|
|
39
|
-
return (server,
|
|
40
|
-
|
|
47
|
+
return (server, source) => {
|
|
48
|
+
const registry = asRegistry(source);
|
|
49
|
+
if (spec.connectionless) {
|
|
50
|
+
const handler = spec.handler;
|
|
51
|
+
const cb = ((args, extra) => handler(args, registry, extra));
|
|
52
|
+
server.tool(spec.name, description, spec.input, cb);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const handler = spec.handler;
|
|
56
|
+
if (spec.version !== undefined && !registry.hasVersion(spec.version)) {
|
|
41
57
|
return;
|
|
58
|
+
}
|
|
59
|
+
// One connection: the input shape is exactly what the tool declares. More
|
|
60
|
+
// than one: every tool takes a `connection` naming the target.
|
|
61
|
+
const input = registry.isSingle
|
|
62
|
+
? spec.input
|
|
63
|
+
: {
|
|
64
|
+
...spec.input,
|
|
65
|
+
connection: z
|
|
66
|
+
.enum(registry.usableNames)
|
|
67
|
+
.describe("Target TM1 connection."),
|
|
68
|
+
};
|
|
42
69
|
// ToolCallback<I> is a conditional type over an unresolved generic, so TS
|
|
43
70
|
// cannot check the lambda against it — the cast asserts what the
|
|
44
71
|
// ToolSpec.handler signature already pins down (same args, same return).
|
|
45
|
-
const cb = ((args, extra) =>
|
|
46
|
-
|
|
72
|
+
const cb = (async (args, extra) => {
|
|
73
|
+
const { connection, ...rest } = args;
|
|
74
|
+
const info = registry.resolveInfo(connection);
|
|
75
|
+
if (!spec.annotations.readOnlyHint && info.mode === "readonly") {
|
|
76
|
+
throw new TM1Error({
|
|
77
|
+
code: TM1ErrorCode.PERMISSION_DENIED,
|
|
78
|
+
message: `${spec.name} changes TM1, but connection "${info.name}" is readonly.`,
|
|
79
|
+
hint: info.modeReason ??
|
|
80
|
+
"Set TM1_MODE=readwrite in that connection's .env to allow writes.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (spec.version !== undefined && info.version !== spec.version) {
|
|
84
|
+
throw new TM1Error({
|
|
85
|
+
code: TM1ErrorCode.UNSUPPORTED_OPERATION,
|
|
86
|
+
message: `${spec.name} needs TM1 v${spec.version}; connection "${info.name}" is v${info.version}.`,
|
|
87
|
+
hint: `Pick a v${spec.version} connection, or use the v${info.version} equivalent.`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const tm1Client = await registry.get(connection);
|
|
91
|
+
return handler(rest, tm1Client, extra);
|
|
92
|
+
});
|
|
93
|
+
server.tool(spec.name, description, input, cb);
|
|
47
94
|
};
|
|
48
95
|
}
|
|
49
96
|
//# sourceMappingURL=define-tool.js.map
|
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { actionResponse } from "../format.js";
|
|
3
|
-
import {
|
|
3
|
+
import { DRY_RUN_CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
|
|
4
4
|
import { DESTRUCTIVE } from "../annotations.js";
|
|
5
5
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
6
6
|
import { defineTool } from "../define-tool.js";
|
|
7
|
+
import { deleteImpact } from "../analysis/delete-impact.js";
|
|
7
8
|
export const registerDeleteDimension = defineTool({
|
|
8
9
|
name: "tm1_delete_dimension",
|
|
9
10
|
description: [
|
|
10
11
|
"Delete a TM1 dimension and all its hierarchies. Warning: fails if the dimension is used in a cube.",
|
|
11
|
-
"Before:
|
|
12
|
+
"Before: dryRun=true lists the cubes that contain it and every process or rule that references it, in one call.",
|
|
12
13
|
],
|
|
13
14
|
annotations: DESTRUCTIVE,
|
|
14
15
|
output: MutationResultSchema,
|
|
15
16
|
input: {
|
|
16
17
|
dimensionName: z.string().describe("Dimension name (case-sensitive)"),
|
|
17
|
-
|
|
18
|
+
dryRun: z
|
|
19
|
+
.boolean()
|
|
20
|
+
.optional()
|
|
21
|
+
.default(false)
|
|
22
|
+
.describe("Delete nothing: return what references the dimension (processes and rules, per source) and the cubes it is part of. Needs no confirm and is not one."),
|
|
23
|
+
...DRY_RUN_CONFIRM_SCHEMA,
|
|
18
24
|
},
|
|
19
|
-
handler: async ({ dimensionName, confirm }, tm1Client) => {
|
|
25
|
+
handler: async ({ dimensionName, dryRun, confirm }, tm1Client) => {
|
|
26
|
+
if (dryRun) {
|
|
27
|
+
return actionResponse({
|
|
28
|
+
success: true,
|
|
29
|
+
dryRun: true,
|
|
30
|
+
dimensionName,
|
|
31
|
+
impact: await deleteImpact(tm1Client, "dimension", dimensionName),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
20
34
|
requireConfirm(confirm, dimensionName, "dimension");
|
|
21
35
|
await tm1Client.dimensions.delete(dimensionName);
|
|
22
36
|
return actionResponse({ success: true, dimensionName });
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type {
|
|
3
|
-
export declare function registerAllTools(server: McpServer,
|
|
2
|
+
import type { ClientSource } from "./define-tool.js";
|
|
3
|
+
export declare function registerAllTools(server: McpServer, source: ClientSource): void;
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/tools/index.js
CHANGED
|
@@ -20,11 +20,7 @@ import { registerTraceFeeders } from "./celldata/trace-feeders.js";
|
|
|
20
20
|
import { registerTraceCellCalculation } from "./celldata/trace-cell-calculation.js";
|
|
21
21
|
// TI development tools (process CRUD, execution, code, params, datasource)
|
|
22
22
|
import { registerExecuteProcess } from "./ti-development/execute-process.js";
|
|
23
|
-
import { registerGetProcessParameters } from "./ti-development/get-process-parameters.js";
|
|
24
|
-
import { registerGetProcessCode } from "./ti-development/get-process-code.js";
|
|
25
23
|
import { registerGetProcess } from "./ti-development/get-process.js";
|
|
26
|
-
import { registerGetProcessDatasource } from "./ti-development/get-process-datasource.js";
|
|
27
|
-
import { registerGetProcessVariables } from "./ti-development/get-process-variables.js";
|
|
28
24
|
import { registerDeleteProcess } from "./ti-development/delete-process.js";
|
|
29
25
|
import { registerCopyProcess } from "./ti-development/copy-process.js";
|
|
30
26
|
import { registerCompileProcess } from "./ti-development/compile-process.js";
|
|
@@ -84,6 +80,7 @@ import { registerCreateChore } from "./scheduling/create-chore.js";
|
|
|
84
80
|
import { registerUpdateChore } from "./scheduling/update-chore.js";
|
|
85
81
|
import { registerDeleteChore } from "./scheduling/delete-chore.js";
|
|
86
82
|
// Operations tools
|
|
83
|
+
import { registerListConnections } from "./operations/list-connections.js";
|
|
87
84
|
import { registerGetMessageLog } from "./operations/get-message-log.js";
|
|
88
85
|
import { registerGetThreads } from "./operations/get-threads.js";
|
|
89
86
|
import { registerGetJobs } from "./operations/get-jobs.js";
|
|
@@ -150,14 +147,10 @@ const REGISTRARS = [
|
|
|
150
147
|
registerTraceFeeders,
|
|
151
148
|
registerTraceCellCalculation,
|
|
152
149
|
registerCheckWritableCoords,
|
|
153
|
-
// TI development — writes go through registerUpsertProcess
|
|
154
|
-
//
|
|
150
|
+
// TI development — writes go through registerUpsertProcess and reads
|
|
151
|
+
// through registerGetProcess (include-flags pick the parts).
|
|
155
152
|
registerExecuteProcess,
|
|
156
|
-
registerGetProcessParameters,
|
|
157
|
-
registerGetProcessCode,
|
|
158
153
|
registerGetProcess,
|
|
159
|
-
registerGetProcessDatasource,
|
|
160
|
-
registerGetProcessVariables,
|
|
161
154
|
registerDeleteProcess,
|
|
162
155
|
registerCopyProcess,
|
|
163
156
|
registerCompileProcess,
|
|
@@ -216,6 +209,7 @@ const REGISTRARS = [
|
|
|
216
209
|
registerUpdateChore,
|
|
217
210
|
registerDeleteChore,
|
|
218
211
|
// Operations
|
|
212
|
+
registerListConnections,
|
|
219
213
|
registerGetMessageLog,
|
|
220
214
|
registerGetThreads,
|
|
221
215
|
registerGetJobs,
|
|
@@ -256,8 +250,8 @@ const REGISTRARS = [
|
|
|
256
250
|
registerAssignClientGroup,
|
|
257
251
|
registerRemoveClientGroup,
|
|
258
252
|
];
|
|
259
|
-
export function registerAllTools(server,
|
|
253
|
+
export function registerAllTools(server, source) {
|
|
260
254
|
for (const register of REGISTRARS)
|
|
261
|
-
register(server,
|
|
255
|
+
register(server, source);
|
|
262
256
|
}
|
|
263
257
|
//# sourceMappingURL=index.js.map
|
|
@@ -11,7 +11,7 @@ export const registerListCubes = defineTool({
|
|
|
11
11
|
name: "tm1_list_cubes",
|
|
12
12
|
description: [
|
|
13
13
|
"List cubes in the TM1 server. Control cubes ('}'-prefixed) excluded unless includeControl=true.",
|
|
14
|
-
"
|
|
14
|
+
"Returns names; includeDimensions / includeRules add each cube's dimensions[] / hasRules. Combinable name filters: nameExact/nameContains/nameRegex.",
|
|
15
15
|
"Paginated (default 50/page).",
|
|
16
16
|
],
|
|
17
17
|
annotations: READ_ONLY,
|
|
@@ -27,8 +27,8 @@ export const registerListCubes = defineTool({
|
|
|
27
27
|
includeDimensions: z
|
|
28
28
|
.boolean()
|
|
29
29
|
.optional()
|
|
30
|
-
.default(
|
|
31
|
-
.describe("Include the dimensions[] array per cube
|
|
30
|
+
.default(false)
|
|
31
|
+
.describe("Include the dimensions[] array per cube."),
|
|
32
32
|
includeRules: z
|
|
33
33
|
.boolean()
|
|
34
34
|
.optional()
|
|
@@ -10,8 +10,8 @@ import { pageShapeFor } from "../schemas/common.js";
|
|
|
10
10
|
export const registerListProcesses = defineTool({
|
|
11
11
|
name: "tm1_list_processes",
|
|
12
12
|
description: [
|
|
13
|
-
"List TurboIntegrator
|
|
14
|
-
"Name filters
|
|
13
|
+
"List TurboIntegrator process names in the TM1 server; fields=['name','parameters'] adds each process's parameters. Control processes ('}'-prefixed) excluded unless includeControl=true.",
|
|
14
|
+
"Name filters: nameContains/nameRegex/nameNotContains/excludePattern.",
|
|
15
15
|
"Paginated (default 50/page).",
|
|
16
16
|
],
|
|
17
17
|
annotations: READ_ONLY,
|
|
@@ -43,14 +43,17 @@ export const registerListProcesses = defineTool({
|
|
|
43
43
|
fields: z
|
|
44
44
|
.array(z.enum(["name", "parameters"]))
|
|
45
45
|
.optional()
|
|
46
|
-
.describe("Projection. Default
|
|
46
|
+
.describe("Projection. Default ['name']; include 'parameters' for parameter lists (~10x larger)."),
|
|
47
47
|
},
|
|
48
48
|
handler: async ({ limit, offset, fetchAll, format, includeControl, nameContains, nameRegex, nameNotContains, excludePattern, fields, }, tm1Client) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// Names only unless parameters are asked for: listing is how a model
|
|
50
|
+
// orients, and the parameter blocks are ~10x the bytes of the names.
|
|
51
|
+
const withParameters = fields?.includes("parameters") ?? false;
|
|
52
|
+
const project = (list) => withParameters ? list : list.map((p) => ({ name: p.name }));
|
|
52
53
|
const fullScan = async () => {
|
|
53
|
-
let processes = await tm1Client.processes.list(
|
|
54
|
+
let processes = await tm1Client.processes.list({
|
|
55
|
+
namesOnly: !withParameters,
|
|
56
|
+
});
|
|
54
57
|
if (!includeControl)
|
|
55
58
|
processes = processes.filter((p) => !p.name.startsWith("}"));
|
|
56
59
|
if (nameContains) {
|
|
@@ -79,6 +82,7 @@ export const registerListProcesses = defineTool({
|
|
|
79
82
|
let page;
|
|
80
83
|
if (canPushDown) {
|
|
81
84
|
const { items, total } = await tm1Client.processes.list({
|
|
85
|
+
namesOnly: !withParameters,
|
|
82
86
|
includeControl,
|
|
83
87
|
...(nameContains ? { nameContains } : {}),
|
|
84
88
|
...(nameNotContains ? { nameNotContains } : {}),
|
|
@@ -3,22 +3,36 @@ import { defineTool } from "../define-tool.js";
|
|
|
3
3
|
import { DESTRUCTIVE } from "../annotations.js";
|
|
4
4
|
import { MutationResultSchema } from "../schemas/items.js";
|
|
5
5
|
import { actionResponse } from "../format.js";
|
|
6
|
-
import {
|
|
6
|
+
import { DRY_RUN_CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
|
|
7
7
|
import { withToolHint } from "../error-format.js";
|
|
8
|
+
import { deleteImpact } from "../analysis/delete-impact.js";
|
|
8
9
|
export const registerDeleteCube = defineTool({
|
|
9
10
|
name: "tm1_delete_cube",
|
|
10
11
|
description: [
|
|
11
12
|
"Delete a TM1 cube and all its data. This action is irreversible.",
|
|
12
13
|
"Safety: pass confirm=<cube name verbatim>. Mismatched confirm rejects the call.",
|
|
13
|
-
"Before:
|
|
14
|
+
"Before: dryRun=true lists every process or rule that references the cube, in one call; tm1_get_cube_stats sizes the data loss.",
|
|
14
15
|
],
|
|
15
16
|
annotations: DESTRUCTIVE,
|
|
16
17
|
output: MutationResultSchema,
|
|
17
18
|
input: {
|
|
18
19
|
cubeName: z.string().describe("Cube name (case-sensitive)"),
|
|
19
|
-
|
|
20
|
+
dryRun: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(false)
|
|
24
|
+
.describe("Delete nothing: return what references the cube (processes and rules, per source). Needs no confirm and is not one."),
|
|
25
|
+
...DRY_RUN_CONFIRM_SCHEMA,
|
|
20
26
|
},
|
|
21
|
-
handler: async ({ cubeName, confirm }, tm1Client) => {
|
|
27
|
+
handler: async ({ cubeName, dryRun, confirm }, tm1Client) => {
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
return actionResponse({
|
|
30
|
+
success: true,
|
|
31
|
+
dryRun: true,
|
|
32
|
+
cubeName,
|
|
33
|
+
impact: await deleteImpact(tm1Client, "cube", cubeName),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
22
36
|
requireConfirm(confirm, cubeName, "cube");
|
|
23
37
|
await withToolHint(tm1Client.cubes.delete(cubeName), "Cube delete failed. Common causes: cube still referenced by TI processes / chores / rules (run tm1_analyze_object_usage to inspect), insufficient permissions, or cube does not exist (tm1_list_cubes to verify name + casing).");
|
|
24
38
|
return actionResponse({ success: true, cubeName });
|
|
@@ -14,7 +14,7 @@ export const registerSetCubeRules = defineTool({
|
|
|
14
14
|
"Create or replace the rules for a TM1 cube.",
|
|
15
15
|
"SKIPCHECK; belongs at the top and FEEDERS; before all feeder definitions — SKIPCHECK is what makes feeders take effect, so rules with feeders need it.",
|
|
16
16
|
"Pass exactly one source: rules (the full text — replaces everything), edits (find/replace patch against the current text; each find must match exactly once, else nothing is written), or filePath (full text from a host file under TM1_LOCAL_FILE_ROOT).",
|
|
17
|
-
"The full resulting text is syntax-checked before anything is written (preflight).
|
|
17
|
+
"The full resulting text is syntax-checked before anything is written (preflight). The stored text is read back after writing (verified.textMatches), so no separate tm1_get_cube_rules is needed; the callgraph cache is dropped automatically.",
|
|
18
18
|
],
|
|
19
19
|
annotations: IDEMPOTENT_DESTRUCTIVE,
|
|
20
20
|
output: MutationResultSchema,
|
|
@@ -65,6 +65,17 @@ export const registerSetCubeRules = defineTool({
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
await withToolHint(tm1Client.cubes.updateRules(cubeName, text), `Inspect details for the offending line.`);
|
|
68
|
+
// Read back what TM1 stored, compared with line endings normalized. The
|
|
69
|
+
// write has landed by now, so a failed read-back is reported, not thrown.
|
|
70
|
+
const norm = (x) => x.replace(/\r\n/g, "\n").trimEnd();
|
|
71
|
+
let verified;
|
|
72
|
+
try {
|
|
73
|
+
const stored = (await tm1Client.cubes.getRules(cubeName)).rulesText;
|
|
74
|
+
verified = { textMatches: norm(stored) === norm(text) };
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
verified = { readBackError: e.message };
|
|
78
|
+
}
|
|
68
79
|
const lineCount = text.split("\n").length;
|
|
69
80
|
// Rule changes shift call edges (DB(), feeders) — drop callgraph TTL early.
|
|
70
81
|
const { cleared: callgraphEntriesCleared } = invalidateCallgraphCache();
|
|
@@ -75,6 +86,7 @@ export const registerSetCubeRules = defineTool({
|
|
|
75
86
|
lineCount,
|
|
76
87
|
...(edits !== undefined ? { editsApplied: edits.length } : {}),
|
|
77
88
|
callgraphEntriesCleared,
|
|
89
|
+
verified,
|
|
78
90
|
});
|
|
79
91
|
},
|
|
80
92
|
});
|
|
@@ -10,7 +10,7 @@ export const registerUnloadCube = defineTool({
|
|
|
10
10
|
// v11 only. v12 answers tm1.Unload with "Demand load, loading and unloading
|
|
11
11
|
// of cubes is no longer supported." — the feature is gone, with no successor
|
|
12
12
|
// endpoint, so the tool is withheld rather than offered and refused.
|
|
13
|
-
|
|
13
|
+
version: 11,
|
|
14
14
|
output: MutationResultSchema,
|
|
15
15
|
input: {
|
|
16
16
|
cubeName: z.string().describe("Name of the cube to unload"),
|
|
@@ -11,7 +11,7 @@ export const registerGetAuditLog = defineTool({
|
|
|
11
11
|
annotations: withVersion(READ_ONLY, "v11"),
|
|
12
12
|
// v12 deprecated AuditLogEntry/AuditLogEntries in 12.0.0 and serves an empty
|
|
13
13
|
// collection with no successor endpoint, so the tool can only mislead there.
|
|
14
|
-
|
|
14
|
+
version: 11,
|
|
15
15
|
output: {
|
|
16
16
|
count: z.number().int(),
|
|
17
17
|
entries: z.array(AuditLogEntrySchema),
|
|
@@ -11,7 +11,7 @@ const registerListJobs = defineTool({
|
|
|
11
11
|
name: "tm1_list_jobs",
|
|
12
12
|
description: "List active jobs (Activity) on a TM1 v12 database — the running tasks that replaced v11 threads. Paginated (default 50/page). (v12 only)",
|
|
13
13
|
annotations: READ_ONLY,
|
|
14
|
-
|
|
14
|
+
version: 12,
|
|
15
15
|
output: pageShapeFor(JobSchema),
|
|
16
16
|
input: { ...PAGINATION_SCHEMA, ...FORMAT_SCHEMA },
|
|
17
17
|
handler: async ({ limit, offset, fetchAll, format }, tm1Client) => {
|
|
@@ -34,7 +34,7 @@ const registerCancelJob = defineTool({
|
|
|
34
34
|
"(v12 only)",
|
|
35
35
|
],
|
|
36
36
|
annotations: DESTRUCTIVE,
|
|
37
|
-
|
|
37
|
+
version: 12,
|
|
38
38
|
output: MutationResultSchema,
|
|
39
39
|
input: {
|
|
40
40
|
jobId: z.string().describe("Job ID to cancel"),
|
|
@@ -10,7 +10,7 @@ export const registerGetMessageLog = defineTool({
|
|
|
10
10
|
// v12 deprecated MessageLogEntry/MessageLogEntries (and the MessageLog /
|
|
11
11
|
// TailMessageLog functions) in 12.0.0; all of them serve empty with no
|
|
12
12
|
// successor endpoint.
|
|
13
|
-
|
|
13
|
+
version: 11,
|
|
14
14
|
output: {
|
|
15
15
|
count: z.number().int(),
|
|
16
16
|
entries: z.array(MessageLogEntrySchema),
|
|
@@ -27,10 +27,19 @@ export const registerGetServerInfo = defineTool({
|
|
|
27
27
|
handler: async ({ format }, tm1Client) => {
|
|
28
28
|
const info = await tm1Client.server.getInfo();
|
|
29
29
|
const x = info.extra ?? {};
|
|
30
|
+
const access = tm1Client.access;
|
|
30
31
|
const payload = {
|
|
31
32
|
// The MCP server's own package identity, so a client can gate on it
|
|
32
33
|
// without parsing the initialize handshake.
|
|
33
|
-
|
|
34
|
+
// mode/environment let a client read what this connection may do and
|
|
35
|
+
// where it points, instead of inferring it from which tools are listed.
|
|
36
|
+
mcpServer: {
|
|
37
|
+
name: NAME,
|
|
38
|
+
version: VERSION,
|
|
39
|
+
mode: access.mode,
|
|
40
|
+
environment: access.environment ?? "unspecified",
|
|
41
|
+
...(access.modeReason ? { modeReason: access.modeReason } : {}),
|
|
42
|
+
},
|
|
34
43
|
serverName: info.serverName,
|
|
35
44
|
productVersion: info.productVersion,
|
|
36
45
|
productEdition: info.productEdition,
|