@ffschrattenecker/tm1-mcp-server 6.1.1 → 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 +35 -1
- package/README.md +23 -5
- package/dist/config.d.ts +9 -0
- package/dist/config.js +89 -64
- 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 -40
- 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 +1 -1
- package/dist/tm1-client.js +2 -11
- package/dist/tools/analysis/audit-naming.js +1 -1
- package/dist/tools/analysis/invalidate-callgraph-cache.js +2 -1
- package/dist/tools/analysis/trace-data-flow.js +1 -1
- package/dist/tools/confirm.js +1 -1
- package/dist/tools/define-tool.d.ts +35 -11
- package/dist/tools/define-tool.js +51 -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/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-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/ti-development/execute-process.js +2 -2
- package/dist/tools/ti-development/import-pro-file.js +1 -1
- package/dist/tools/ti-development/search-code.js +1 -1
- package/dist/tools/ti-development/upsert-process.js +2 -2
- package/dist/tools/with-annotations.d.ts +10 -0
- package/dist/tools/with-annotations.js +58 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -3
- 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
|
@@ -14,19 +14,24 @@
|
|
|
14
14
|
// resources/unsubscribe, notifications/resources/updated).
|
|
15
15
|
import { SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
import { tm1Events } from "../lib/tm1-events.js";
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
17
|
+
// The server-state resource aggregates counts across cubes/dims/processes/
|
|
18
|
+
// chores/clients — almost any non-safe call can change it. With several
|
|
19
|
+
// connections there is one such resource per connection; stateUris maps each
|
|
20
|
+
// to the connectionId whose mutations affect it.
|
|
21
|
+
const SINGLE_STATE_URIS = new Map([
|
|
22
|
+
["tm1://server/state", ""],
|
|
23
|
+
]);
|
|
21
24
|
export class SubscriptionRegistry {
|
|
22
25
|
server;
|
|
23
26
|
logger;
|
|
27
|
+
stateUris;
|
|
24
28
|
// URIs the client has asked us to push updates for.
|
|
25
29
|
subscribed = new Set();
|
|
26
30
|
mutationListener;
|
|
27
|
-
constructor(server, logger) {
|
|
31
|
+
constructor(server, logger, stateUris = SINGLE_STATE_URIS) {
|
|
28
32
|
this.server = server;
|
|
29
33
|
this.logger = logger;
|
|
34
|
+
this.stateUris = stateUris;
|
|
30
35
|
}
|
|
31
36
|
/** Install subscribe/unsubscribe request handlers + mutation listener. */
|
|
32
37
|
install() {
|
|
@@ -62,13 +67,17 @@ export class SubscriptionRegistry {
|
|
|
62
67
|
size() {
|
|
63
68
|
return this.subscribed.size;
|
|
64
69
|
}
|
|
65
|
-
onMutation(
|
|
70
|
+
onMutation(e) {
|
|
66
71
|
// Iterate subscribed URIs and notify those affected by this mutation.
|
|
67
72
|
// Currently only the aggregate state resource is mutation-sensitive;
|
|
68
73
|
// future entries can branch on e.path / e.method for finer-grained
|
|
69
74
|
// notifications (e.g. tm1://process/{name}/code on /Processes mutations).
|
|
70
75
|
for (const uri of this.subscribed) {
|
|
71
|
-
|
|
76
|
+
const target = this.stateUris.get(uri);
|
|
77
|
+
if (target === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
// An empty target or an untagged event matches any connection.
|
|
80
|
+
if (target && e.connectionId && target !== e.connectionId)
|
|
72
81
|
continue;
|
|
73
82
|
// Fire-and-forget; missing transport (server not connected) throws.
|
|
74
83
|
this.server.server.sendResourceUpdated({ uri }).catch((err) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Sent once per session (MCP `instructions`), so every sentence here is paid
|
|
2
|
+
// for by every conversation — keep it to what a model cannot infer from the
|
|
3
|
+
// tool list itself.
|
|
4
|
+
export function serverInstructions(registry) {
|
|
5
|
+
const lines = ["TM1 / IBM Planning Analytics server."];
|
|
6
|
+
if (!registry.isSingle) {
|
|
7
|
+
lines.push("Every tool except tm1_list_connections takes a `connection`; if the target is unclear, call tm1_list_connections and ask. Never write to a connection the user did not name.");
|
|
8
|
+
}
|
|
9
|
+
lines.push("Keep results small: filter by name, use projections (fields, include* flags, compact) and countOnly before paging.");
|
|
10
|
+
return lines.join(" ");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=server-instructions.js.map
|
package/dist/tm1-client/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { connectionIdOf } from "../config.js";
|
|
1
2
|
import { createConnectionProfile, } from "./connection/profile.js";
|
|
2
3
|
import { TM1Error, TM1ErrorCode } from "../types.js";
|
|
3
4
|
import { PRODUCT, VERSION } from "../version.js";
|
|
@@ -110,13 +111,21 @@ export class TM1HttpClient {
|
|
|
110
111
|
}
|
|
111
112
|
const retryResult = await this.handleResponse(retryResponse, path);
|
|
112
113
|
if (!isSafeMethod) {
|
|
113
|
-
tm1Events.emit("mutation", {
|
|
114
|
+
tm1Events.emit("mutation", {
|
|
115
|
+
method,
|
|
116
|
+
path,
|
|
117
|
+
connectionId: connectionIdOf(this.config),
|
|
118
|
+
});
|
|
114
119
|
}
|
|
115
120
|
return retryResult;
|
|
116
121
|
}
|
|
117
122
|
const result = await this.handleResponse(response, path);
|
|
118
123
|
if (!isSafeMethod) {
|
|
119
|
-
tm1Events.emit("mutation", {
|
|
124
|
+
tm1Events.emit("mutation", {
|
|
125
|
+
method,
|
|
126
|
+
path,
|
|
127
|
+
connectionId: connectionIdOf(this.config),
|
|
128
|
+
});
|
|
120
129
|
}
|
|
121
130
|
return result;
|
|
122
131
|
}
|
|
@@ -191,7 +200,11 @@ export class TM1HttpClient {
|
|
|
191
200
|
}
|
|
192
201
|
const text = await response.text();
|
|
193
202
|
if (!isSafeHttpMethod(method)) {
|
|
194
|
-
tm1Events.emit("mutation", {
|
|
203
|
+
tm1Events.emit("mutation", {
|
|
204
|
+
method,
|
|
205
|
+
path,
|
|
206
|
+
connectionId: connectionIdOf(this.config),
|
|
207
|
+
});
|
|
195
208
|
}
|
|
196
209
|
return text;
|
|
197
210
|
}
|
|
@@ -225,7 +238,11 @@ export class TM1HttpClient {
|
|
|
225
238
|
throw this.classifyHttpError(response.status, path, errBody || undefined);
|
|
226
239
|
}
|
|
227
240
|
if (!isSafeHttpMethod(method)) {
|
|
228
|
-
tm1Events.emit("mutation", {
|
|
241
|
+
tm1Events.emit("mutation", {
|
|
242
|
+
method,
|
|
243
|
+
path,
|
|
244
|
+
connectionId: connectionIdOf(this.config),
|
|
245
|
+
});
|
|
229
246
|
}
|
|
230
247
|
}
|
|
231
248
|
/**
|
|
@@ -12,6 +12,12 @@ export interface ProcessVariableLayout {
|
|
|
12
12
|
export interface ProcessListOpts extends NameFilterOpts {
|
|
13
13
|
/** Set to slice server-side. Only legal when every active filter is in NameFilterOpts. */
|
|
14
14
|
page?: PageOpts;
|
|
15
|
+
/**
|
|
16
|
+
* Select Name only; every process comes back with `parameters: []`. TM1
|
|
17
|
+
* then serializes no parameter blocks at all — for callers that only need
|
|
18
|
+
* names (counts, existence checks, listings) this is the cheap query.
|
|
19
|
+
*/
|
|
20
|
+
namesOnly?: boolean;
|
|
15
21
|
}
|
|
16
22
|
export declare class ProcessService {
|
|
17
23
|
private readonly http;
|
|
@@ -29,7 +35,7 @@ export declare class ProcessService {
|
|
|
29
35
|
* Name-only when v11 rejects the inline Parameters select.
|
|
30
36
|
* GET /api/v1/Processes?$select=Name,Parameters
|
|
31
37
|
*/
|
|
32
|
-
list(): Promise<Process[]>;
|
|
38
|
+
list(opts?: Pick<ProcessListOpts, "namesOnly">): Promise<Process[]>;
|
|
33
39
|
/**
|
|
34
40
|
* Paged variant: with `page` set the slice happens server-side
|
|
35
41
|
* (`$orderby=Name&$top&$skip&$count=true`) and the return carries the
|
|
@@ -85,6 +85,12 @@ export class ProcessService {
|
|
|
85
85
|
// — TM1 v11 rejects $expand=Parameters with a syntax error. Use $select
|
|
86
86
|
// instead, which returns Parameters inline. Param.Type comes back as the
|
|
87
87
|
// already-decoded string "Numeric" / "String" (not the legacy int code).
|
|
88
|
+
const namesOnly = async () => {
|
|
89
|
+
const response = await this.http.request("GET", `/api/v1/Processes?$select=Name${query}`);
|
|
90
|
+
return wrap(response.value.map((p) => ({ name: p.Name, parameters: [] })), readCount(response));
|
|
91
|
+
};
|
|
92
|
+
if (opts.namesOnly)
|
|
93
|
+
return namesOnly();
|
|
88
94
|
try {
|
|
89
95
|
const response = await this.http.request("GET", `/api/v1/Processes?$select=Name,Parameters${query}`);
|
|
90
96
|
return wrap(response.value.map((p) => ({
|
|
@@ -99,11 +105,7 @@ export class ProcessService {
|
|
|
99
105
|
}
|
|
100
106
|
catch (e) {
|
|
101
107
|
rethrowIfSystemic(e);
|
|
102
|
-
|
|
103
|
-
return wrap(response.value.map((p) => ({
|
|
104
|
-
name: p.Name,
|
|
105
|
-
parameters: [],
|
|
106
|
-
})), readCount(response));
|
|
108
|
+
return namesOnly();
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
/**
|
package/dist/tm1-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type pino from "pino";
|
|
2
|
-
import type
|
|
2
|
+
import { type TM1Config } from "./config.js";
|
|
3
3
|
import type { SessionManager } from "./session-manager.js";
|
|
4
4
|
import { BatchService } from "./tm1-client/services/batch-service.js";
|
|
5
5
|
import { CubeService } from "./tm1-client/services/cube-service.js";
|
package/dist/tm1-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { connectionIdOf } from "./config.js";
|
|
1
2
|
import { TM1HttpClient } from "./tm1-client/http.js";
|
|
2
3
|
import { BatchService } from "./tm1-client/services/batch-service.js";
|
|
3
4
|
import { CubeService } from "./tm1-client/services/cube-service.js";
|
|
@@ -95,17 +96,7 @@ export class TM1Client {
|
|
|
95
96
|
* process backups) apart: host and port, plus the v12 instance/database.
|
|
96
97
|
*/
|
|
97
98
|
get connectionId() {
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const url = new URL(this.config.baseUrl);
|
|
101
|
-
host = url.port ? `${url.hostname}_${url.port}` : url.hostname;
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
host = this.config.baseUrl;
|
|
105
|
-
}
|
|
106
|
-
return [host, this.config.instance, this.config.database]
|
|
107
|
-
.filter((part) => Boolean(part))
|
|
108
|
-
.join("_");
|
|
99
|
+
return connectionIdOf(this.config);
|
|
109
100
|
}
|
|
110
101
|
/**
|
|
111
102
|
* Authenticate and start the keep-alive timer.
|
|
@@ -240,7 +240,7 @@ export const registerAuditNaming = defineTool({
|
|
|
240
240
|
// ── Processes + (optional) variables ───────────────────────────────
|
|
241
241
|
let processNames = [];
|
|
242
242
|
if (want("processes") || want("processVariables")) {
|
|
243
|
-
const procs = await tm1Client.processes.list();
|
|
243
|
+
const procs = await tm1Client.processes.list({ namesOnly: true });
|
|
244
244
|
processNames = (includeControl ? procs : procs.filter((p) => !isControlName(p.name))).map((p) => p.name);
|
|
245
245
|
}
|
|
246
246
|
if (want("processes")) {
|
|
@@ -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",
|
package/dist/tools/confirm.js
CHANGED
|
@@ -27,7 +27,7 @@ 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
31
|
};
|
|
32
32
|
// For a destructive tool with a dryRun: a dry run needs no confirm, so the
|
|
33
33
|
// field is optional in the schema and required at runtime on the real call.
|
|
@@ -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
|
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 } : {}),
|
|
@@ -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),
|
|
@@ -47,7 +47,7 @@ export const registerGetServerState = defineTool({
|
|
|
47
47
|
tm1Client.server.getInfo(),
|
|
48
48
|
tm1Client.cubes.list(),
|
|
49
49
|
tm1Client.dimensions.list(),
|
|
50
|
-
tm1Client.processes.list(),
|
|
50
|
+
tm1Client.processes.list({ namesOnly: true }),
|
|
51
51
|
tm1Client.chores.list(),
|
|
52
52
|
tm1Client.security.listClients(),
|
|
53
53
|
]);
|
|
@@ -9,7 +9,7 @@ const registerListThreads = defineTool({
|
|
|
9
9
|
name: "tm1_list_threads",
|
|
10
10
|
description: "List active threads on the TM1 server (running processes, chores, MDX queries, etc.). Paginated (default 50/page). (v11 only)",
|
|
11
11
|
annotations: READ_ONLY,
|
|
12
|
-
|
|
12
|
+
version: 11,
|
|
13
13
|
output: pageShapeFor(ThreadSchema),
|
|
14
14
|
input: { ...PAGINATION_SCHEMA, ...FORMAT_SCHEMA },
|
|
15
15
|
handler: async ({ limit, offset, fetchAll, format }, tm1Client) => {
|
|
@@ -33,7 +33,7 @@ const registerCancelThread = defineTool({
|
|
|
33
33
|
"(v11 only)",
|
|
34
34
|
],
|
|
35
35
|
annotations: DESTRUCTIVE,
|
|
36
|
-
|
|
36
|
+
version: 11,
|
|
37
37
|
output: MutationResultSchema,
|
|
38
38
|
input: {
|
|
39
39
|
id: z.number().int().describe("Thread ID to cancel"),
|
|
@@ -10,7 +10,7 @@ export const registerGetTransactionLog = defineTool({
|
|
|
10
10
|
// v12 deprecated TransactionLogEntry/TransactionLogEntries (and the
|
|
11
11
|
// TransactionLog / TailTransactionLog functions) in 12.0.0; all of them serve
|
|
12
12
|
// empty with no successor endpoint.
|
|
13
|
-
|
|
13
|
+
version: 11,
|
|
14
14
|
output: {
|
|
15
15
|
count: z.number().int(),
|
|
16
16
|
coverage: z
|