@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +110 -1
  2. package/README.md +28 -7
  3. package/dist/config.d.ts +12 -1
  4. package/dist/config.js +120 -79
  5. package/dist/connections.d.ts +69 -0
  6. package/dist/connections.js +262 -0
  7. package/dist/http-transport.d.ts +2 -2
  8. package/dist/index.js +45 -37
  9. package/dist/lib/callgraph/tm1-adapter.d.ts +4 -1
  10. package/dist/lib/callgraph/tm1-adapter.js +5 -2
  11. package/dist/lib/slim-json-schema.d.ts +8 -0
  12. package/dist/lib/slim-json-schema.js +34 -0
  13. package/dist/lib/strip-comments.js +1 -1
  14. package/dist/lib/tm1-events.d.ts +2 -0
  15. package/dist/prompts/index.js +2 -2
  16. package/dist/resources/index.d.ts +4 -2
  17. package/dist/resources/index.js +93 -53
  18. package/dist/resources/subscriptions.d.ts +2 -1
  19. package/dist/resources/subscriptions.js +16 -7
  20. package/dist/server-instructions.d.ts +3 -0
  21. package/dist/server-instructions.js +12 -0
  22. package/dist/tm1-client/http.d.ts +1 -1
  23. package/dist/tm1-client/http.js +21 -4
  24. package/dist/tm1-client/services/process-service.d.ts +7 -1
  25. package/dist/tm1-client/services/process-service.js +7 -5
  26. package/dist/tm1-client.d.ts +7 -1
  27. package/dist/tm1-client.js +10 -11
  28. package/dist/tools/analysis/analyze-object-usage.d.ts +10 -0
  29. package/dist/tools/analysis/analyze-object-usage.js +37 -33
  30. package/dist/tools/analysis/audit-naming.js +1 -1
  31. package/dist/tools/analysis/delete-impact.d.ts +13 -0
  32. package/dist/tools/analysis/delete-impact.js +30 -0
  33. package/dist/tools/analysis/invalidate-callgraph-cache.js +2 -1
  34. package/dist/tools/analysis/trace-data-flow.js +1 -1
  35. package/dist/tools/celldata/write-cells.js +37 -2
  36. package/dist/tools/confirm.d.ts +4 -1
  37. package/dist/tools/confirm.js +12 -2
  38. package/dist/tools/define-tool.d.ts +35 -11
  39. package/dist/tools/define-tool.js +51 -4
  40. package/dist/tools/dimension-management/delete-dimension.js +18 -4
  41. package/dist/tools/index.d.ts +2 -2
  42. package/dist/tools/index.js +6 -12
  43. package/dist/tools/metadata/list-cubes.js +3 -3
  44. package/dist/tools/metadata/list-processes.js +11 -7
  45. package/dist/tools/model-building/delete-cube.js +18 -4
  46. package/dist/tools/model-building/set-cube-rules.js +13 -1
  47. package/dist/tools/model-building/unload-cube.js +1 -1
  48. package/dist/tools/operations/get-audit-log.js +1 -1
  49. package/dist/tools/operations/get-jobs.js +2 -2
  50. package/dist/tools/operations/get-message-log.js +1 -1
  51. package/dist/tools/operations/get-server-info.js +10 -1
  52. package/dist/tools/operations/get-server-state.js +1 -1
  53. package/dist/tools/operations/get-threads.js +2 -2
  54. package/dist/tools/operations/get-transaction-log.js +1 -1
  55. package/dist/tools/operations/list-connections.d.ts +2 -0
  56. package/dist/tools/operations/list-connections.js +35 -0
  57. package/dist/tools/operations/save-data.js +1 -1
  58. package/dist/tools/schemas/items-monitoring.d.ts +11 -0
  59. package/dist/tools/schemas/items-monitoring.js +9 -1
  60. package/dist/tools/schemas/items-processes.d.ts +3 -0
  61. package/dist/tools/schemas/items-processes.js +2 -1
  62. package/dist/tools/ti-development/diff-processes.d.ts +42 -0
  63. package/dist/tools/ti-development/diff-processes.js +4 -4
  64. package/dist/tools/ti-development/execute-process.js +29 -4
  65. package/dist/tools/ti-development/import-pro-file.js +1 -1
  66. package/dist/tools/ti-development/preflight.d.ts +24 -0
  67. package/dist/tools/ti-development/preflight.js +32 -0
  68. package/dist/tools/ti-development/search-code.js +1 -1
  69. package/dist/tools/ti-development/upsert-process.js +100 -11
  70. package/dist/tools/with-annotations.d.ts +10 -0
  71. package/dist/tools/with-annotations.js +58 -3
  72. package/npm-shrinkwrap.json +4586 -0
  73. package/package.json +9 -7
  74. package/dist/tools/ti-development/get-process-code.d.ts +0 -2
  75. package/dist/tools/ti-development/get-process-code.js +0 -100
  76. package/dist/tools/ti-development/get-process-datasource.d.ts +0 -2
  77. package/dist/tools/ti-development/get-process-datasource.js +0 -20
  78. package/dist/tools/ti-development/get-process-parameters.d.ts +0 -2
  79. package/dist/tools/ti-development/get-process-parameters.js +0 -25
  80. package/dist/tools/ti-development/get-process-variables.d.ts +0 -2
  81. 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× get_process_code.",
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
- "Related: tm1_clear_cube for bulk wipe, tm1_get_cell_value to read back, tm1_execute_process for production data loads.",
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
- await tm1Client.cells.writeCells(cubeName, address.dimensions, cells.map((c) => ({ ...c, elements: address.toCubeOrder(c.elements) })));
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
  : {}),
@@ -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 function requireConfirm(provided: string, target: string, kind: string): void;
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
  };
@@ -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("Safety check: repeat the target identifier verbatim (e.g. cube/dimension/process name) to confirm this irreversible action. Mismatched values reject the call."),
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: `confirm mismatch — expected ${kind} name "${target}", got "${provided}".`,
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 { ZodObject, ZodRawShape, ZodTypeAny } from "zod";
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, tm1Client: TM1Client) => void;
7
- export interface ToolSpec<I extends ZodRawShape> {
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
- * Registration gate for tools that exist on one TM1 generation only (v11
28
- * threads vs v12 jobs, v11-only save_data). Returning false skips the tool
29
- * entirely — it never appears in tools/list. This is deliberately separate
30
- * from the `requiresVersion` annotation, which is a client-facing HINT and
31
- * is also carried by tools that stay registered on both generations.
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
- enabled?: (tm1Client: TM1Client) => boolean;
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 shared TM1 client, and the SDK's
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, tm1Client) => {
40
- if (spec.enabled && !spec.enabled(tm1Client))
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) => spec.handler(args, tm1Client, extra));
46
- server.tool(spec.name, description, spec.input, cb);
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 { CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
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: tm1_find_orphan_dimensions to confirm the dimension is unused, or tm1_analyze_object_usage for a targeted check.",
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
- ...CONFIRM_SCHEMA,
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 });
@@ -1,4 +1,4 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { TM1Client } from "../tm1-client.js";
3
- export declare function registerAllTools(server: McpServer, tm1Client: TM1Client): void;
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
@@ -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 (bundled),
154
- // reads remain atomic for inspection without pulling full process code.
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, tm1Client) {
253
+ export function registerAllTools(server, source) {
260
254
  for (const register of REGISTRARS)
261
- register(server, tm1Client);
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
- "Combinable name filters (nameExact/nameContains/nameRegex) and projection toggles (includeDimensions, includeRules) trim payload on wide models.",
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(true)
31
- .describe("Include the dimensions[] array per cube (default: true). Set false for compact output on wide cubes."),
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 processes (with parameters) in the TM1 server. Control processes ('}'-prefixed) excluded unless includeControl=true.",
14
- "Name filters (nameContains/nameRegex/nameNotContains/excludePattern) and fields=['name'] projection trim payload on large models.",
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: all fields. Use ['name'] to skip parameters[] and shrink payload ~10x."),
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
- const project = (list) => fields && !fields.includes("parameters")
50
- ? list.map((p) => ({ name: p.name }))
51
- : list;
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 { CONFIRM_SCHEMA, requireConfirm } from "../confirm.js";
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: tm1_analyze_object_usage to find rules or processes referencing the cube; tm1_get_cube_stats to size the data loss.",
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
- ...CONFIRM_SCHEMA,
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). After: tm1_get_cube_rules to read back; the callgraph cache is dropped automatically.",
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
- enabled: (tm1Client) => tm1Client.version === 11,
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
- enabled: (tm1Client) => tm1Client.version === 11,
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
- enabled: (tm1Client) => tm1Client.version === 12,
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
- enabled: (tm1Client) => tm1Client.version === 12,
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
- enabled: (tm1Client) => tm1Client.version === 11,
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
- mcpServer: { name: NAME, version: VERSION },
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,