@node-i3x/mcp 0.0.0-stage → 0.11.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/README.md +123 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +836 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +932 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
// src/utils/similarity.ts
|
|
2
|
+
function computeSimilarity(a, b) {
|
|
3
|
+
const s1 = a.toLowerCase().trim();
|
|
4
|
+
const s2 = b.toLowerCase().trim();
|
|
5
|
+
if (s1 === s2) return 1;
|
|
6
|
+
if (!s1 || !s2) return 0;
|
|
7
|
+
if (s1.includes(s2) || s2.includes(s1)) {
|
|
8
|
+
const ratio = Math.min(s1.length, s2.length) / Math.max(s1.length, s2.length);
|
|
9
|
+
return Math.max(0.7, ratio);
|
|
10
|
+
}
|
|
11
|
+
const m = s1.length;
|
|
12
|
+
const n = s2.length;
|
|
13
|
+
const dp = Array.from({ length: n + 1 }, (_, i) => i);
|
|
14
|
+
for (let i = 1; i <= m; i++) {
|
|
15
|
+
let prev = dp[0];
|
|
16
|
+
dp[0] = i;
|
|
17
|
+
for (let j = 1; j <= n; j++) {
|
|
18
|
+
const temp = dp[j];
|
|
19
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
20
|
+
dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + cost);
|
|
21
|
+
prev = temp;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const distance = dp[n];
|
|
25
|
+
const maxLen = Math.max(m, n);
|
|
26
|
+
return Math.max(0, 1 - distance / maxLen);
|
|
27
|
+
}
|
|
28
|
+
function rankSuggestions(query, candidates, topN = 5, minScore = 0.4) {
|
|
29
|
+
return candidates.map((candidate) => ({
|
|
30
|
+
candidate,
|
|
31
|
+
score: computeSimilarity(query, candidate)
|
|
32
|
+
})).filter((item) => item.score >= minScore).sort((a, b) => b.score - a.score).slice(0, topN).map((item) => item.candidate);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/bindings/connector-binding.ts
|
|
36
|
+
var InProcessConnectorBinding = class {
|
|
37
|
+
constructor(stack) {
|
|
38
|
+
this.stack = stack;
|
|
39
|
+
}
|
|
40
|
+
stack;
|
|
41
|
+
async getInfo() {
|
|
42
|
+
return {
|
|
43
|
+
specVersion: "1.0.0-beta",
|
|
44
|
+
serverVersion: "0.10.0",
|
|
45
|
+
serverName: "node-i3x (In-Process Direct)",
|
|
46
|
+
capabilities: {
|
|
47
|
+
query: { history: true },
|
|
48
|
+
update: { current: true, history: true },
|
|
49
|
+
subscribe: { stream: true }
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async getNamespaces() {
|
|
54
|
+
const model = await this.stack.modelService.getOrBuildModel();
|
|
55
|
+
const uris = /* @__PURE__ */ new Set();
|
|
56
|
+
for (const node of model.nodesById.values()) {
|
|
57
|
+
if (node.namespaceUri) uris.add(node.namespaceUri);
|
|
58
|
+
}
|
|
59
|
+
return Array.from(uris).map((uri) => ({
|
|
60
|
+
uri,
|
|
61
|
+
displayName: uri
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
async getObjectTypes(namespaceUri) {
|
|
65
|
+
return this.stack.typeService.getObjectTypes(namespaceUri);
|
|
66
|
+
}
|
|
67
|
+
async getRelationshipTypes() {
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
elementId: "HasComponent",
|
|
71
|
+
displayName: "Has Component",
|
|
72
|
+
namespaceUri: "http://opcfoundation.org/UA/",
|
|
73
|
+
relationshipId: "i=47",
|
|
74
|
+
reverseOf: "ComponentOf"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
elementId: "HasProperty",
|
|
78
|
+
displayName: "Has Property",
|
|
79
|
+
namespaceUri: "http://opcfoundation.org/UA/",
|
|
80
|
+
relationshipId: "i=46",
|
|
81
|
+
reverseOf: "PropertyOf"
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
async getObjects(options) {
|
|
86
|
+
const model = await this.stack.modelService.getOrBuildModel();
|
|
87
|
+
let nodes;
|
|
88
|
+
if (options?.rootOnly) {
|
|
89
|
+
nodes = model.rootIds.map((id) => model.nodesById.get(id)).filter((n) => n !== void 0 && n.kind === "instance");
|
|
90
|
+
} else {
|
|
91
|
+
nodes = Array.from(model.nodesById.values()).filter(
|
|
92
|
+
(n) => n.kind === "instance" || n.kind === "property"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (options?.typeElementId) {
|
|
96
|
+
nodes = nodes.filter((n) => n.type === options.typeElementId);
|
|
97
|
+
}
|
|
98
|
+
const rootSet = new Set(model.rootIds);
|
|
99
|
+
return nodes.map((n) => ({
|
|
100
|
+
elementId: n.id,
|
|
101
|
+
displayName: n.name,
|
|
102
|
+
typeElementId: n.type || "BaseObjectType",
|
|
103
|
+
parentId: rootSet.has(n.id) ? null : void 0,
|
|
104
|
+
isComposition: n.kind === "instance",
|
|
105
|
+
metadata: {
|
|
106
|
+
typeNamespaceUri: n.namespaceUri,
|
|
107
|
+
engUnit: n.engUnit || null,
|
|
108
|
+
sourceTypeId: n.sourceTypeId || null
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
async getObjectById(elementId) {
|
|
113
|
+
const objects = await this.getObjects();
|
|
114
|
+
return objects.find((o) => o.elementId === elementId) || null;
|
|
115
|
+
}
|
|
116
|
+
async getRelatedObjects(elementId) {
|
|
117
|
+
const model = await this.stack.modelService.getOrBuildModel();
|
|
118
|
+
const node = model.nodesById.get(elementId);
|
|
119
|
+
if (!node?.children) return [];
|
|
120
|
+
const children = node.children.map((childId) => model.nodesById.get(childId)).filter((n) => n !== void 0);
|
|
121
|
+
return children.map((n) => ({
|
|
122
|
+
elementId: n.id,
|
|
123
|
+
displayName: n.name,
|
|
124
|
+
typeElementId: n.type || "BaseObjectType",
|
|
125
|
+
parentId: elementId,
|
|
126
|
+
isComposition: n.kind === "instance",
|
|
127
|
+
metadata: {
|
|
128
|
+
typeNamespaceUri: n.namespaceUri,
|
|
129
|
+
engUnit: n.engUnit || null,
|
|
130
|
+
sourceTypeId: n.sourceTypeId || null
|
|
131
|
+
}
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
async getObjectValues(elementIds, maxDepth = 1) {
|
|
135
|
+
const bulkResults = await this.stack.valueService.readValues(elementIds, maxDepth);
|
|
136
|
+
return bulkResults.map((r) => r.result).filter((v) => v !== null && v !== void 0);
|
|
137
|
+
}
|
|
138
|
+
async getHistoricalValues(elementIds, startTime, endTime, maxDepth = 1) {
|
|
139
|
+
const start = startTime ? new Date(startTime) : null;
|
|
140
|
+
const end = endTime ? new Date(endTime) : null;
|
|
141
|
+
const bulkResults = await this.stack.historyService.readHistory(
|
|
142
|
+
elementIds,
|
|
143
|
+
start,
|
|
144
|
+
end,
|
|
145
|
+
maxDepth
|
|
146
|
+
);
|
|
147
|
+
return bulkResults.map((r) => r.result).filter((v) => v !== null && v !== void 0);
|
|
148
|
+
}
|
|
149
|
+
async findSuggestions(unknownId) {
|
|
150
|
+
try {
|
|
151
|
+
const allObjects = await this.getObjects();
|
|
152
|
+
const candidates = allObjects.map((o) => o.elementId);
|
|
153
|
+
return rankSuggestions(unknownId, candidates, 5, 0.4);
|
|
154
|
+
} catch {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/bindings/rest-binding.ts
|
|
161
|
+
var RestClientBinding = class {
|
|
162
|
+
baseUrl;
|
|
163
|
+
headers;
|
|
164
|
+
requestTimeoutMs;
|
|
165
|
+
constructor(baseUrl = "http://localhost:3000", token, timeoutMs = 15e3) {
|
|
166
|
+
let cleanUrl = baseUrl.trim().replace(/\/+$/, "");
|
|
167
|
+
if (!cleanUrl.startsWith("http://") && !cleanUrl.startsWith("https://")) {
|
|
168
|
+
cleanUrl = `http://${cleanUrl}`;
|
|
169
|
+
}
|
|
170
|
+
this.baseUrl = cleanUrl;
|
|
171
|
+
this.requestTimeoutMs = timeoutMs;
|
|
172
|
+
this.headers = {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
Accept: "application/json"
|
|
175
|
+
};
|
|
176
|
+
if (token) {
|
|
177
|
+
this.headers.Authorization = `Bearer ${token}`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async request(path, options = {}) {
|
|
181
|
+
const url = `${this.baseUrl}${path}`;
|
|
182
|
+
const signal = options.signal || AbortSignal.timeout(this.requestTimeoutMs);
|
|
183
|
+
try {
|
|
184
|
+
const response = await fetch(url, {
|
|
185
|
+
...options,
|
|
186
|
+
signal,
|
|
187
|
+
headers: {
|
|
188
|
+
...this.headers,
|
|
189
|
+
...options.headers || {}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
const errorText = await response.text();
|
|
194
|
+
const truncated = errorText.length > 300 ? `${errorText.slice(0, 300)}...` : errorText;
|
|
195
|
+
throw new Error(`HTTP ${response.status} from ${path}: ${truncated}`);
|
|
196
|
+
}
|
|
197
|
+
return await response.json();
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (err.name === "TimeoutError") {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`i3X Gateway request timed out after ${this.requestTimeoutMs}ms for ${path}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async getInfo() {
|
|
208
|
+
return this.request("/v1/info");
|
|
209
|
+
}
|
|
210
|
+
async getNamespaces() {
|
|
211
|
+
const res = await this.request("/v1/namespaces");
|
|
212
|
+
return res.namespaces || [];
|
|
213
|
+
}
|
|
214
|
+
async getObjectTypes(namespaceUri) {
|
|
215
|
+
const query = namespaceUri ? `?namespaceUri=${encodeURIComponent(namespaceUri)}` : "";
|
|
216
|
+
const res = await this.request(
|
|
217
|
+
`/v1/objecttypes${query}`
|
|
218
|
+
);
|
|
219
|
+
return res.objectTypes || [];
|
|
220
|
+
}
|
|
221
|
+
async getRelationshipTypes() {
|
|
222
|
+
const res = await this.request(
|
|
223
|
+
"/v1/relationshiptypes"
|
|
224
|
+
);
|
|
225
|
+
return res.relationshipTypes || [];
|
|
226
|
+
}
|
|
227
|
+
async getObjects(options) {
|
|
228
|
+
const params = new URLSearchParams();
|
|
229
|
+
if (options?.rootOnly) params.set("root", "true");
|
|
230
|
+
if (options?.typeElementId) params.set("typeElementId", options.typeElementId);
|
|
231
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
232
|
+
const res = await this.request(
|
|
233
|
+
`/v1/objects${query}`
|
|
234
|
+
);
|
|
235
|
+
return res.objects || [];
|
|
236
|
+
}
|
|
237
|
+
async getObjectById(elementId) {
|
|
238
|
+
try {
|
|
239
|
+
const res = await this.request(
|
|
240
|
+
"/v1/objects/list",
|
|
241
|
+
{
|
|
242
|
+
method: "POST",
|
|
243
|
+
body: JSON.stringify({ elementIds: [elementId] })
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
if (res.objects && res.objects.length > 0) {
|
|
247
|
+
return res.objects[0] ?? null;
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async getRelatedObjects(elementId, relationshipType) {
|
|
255
|
+
try {
|
|
256
|
+
const body = { elementId };
|
|
257
|
+
if (relationshipType) body.relationshipType = relationshipType;
|
|
258
|
+
const res = await this.request("/v1/objects/related", {
|
|
259
|
+
method: "POST",
|
|
260
|
+
body: JSON.stringify(body)
|
|
261
|
+
});
|
|
262
|
+
return (res.relatedObjects || []).map((r) => r.targetObject);
|
|
263
|
+
} catch {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async getObjectValues(elementIds, maxDepth = 1) {
|
|
268
|
+
const res = await this.request(
|
|
269
|
+
"/v1/objects/value",
|
|
270
|
+
{
|
|
271
|
+
method: "POST",
|
|
272
|
+
body: JSON.stringify({ elementIds, maxDepth })
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
return res.values || [];
|
|
276
|
+
}
|
|
277
|
+
async getHistoricalValues(elementIds, startTime, endTime, maxDepth = 1) {
|
|
278
|
+
const res = await this.request(
|
|
279
|
+
"/v1/objects/history",
|
|
280
|
+
{
|
|
281
|
+
method: "POST",
|
|
282
|
+
body: JSON.stringify({ elementIds, startTime, endTime, maxDepth })
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
return res.history || [];
|
|
286
|
+
}
|
|
287
|
+
async findSuggestions(unknownId) {
|
|
288
|
+
try {
|
|
289
|
+
const allObjects = await this.getObjects();
|
|
290
|
+
const candidates = allObjects.map((o) => o.elementId);
|
|
291
|
+
return rankSuggestions(unknownId, candidates, 5, 0.4);
|
|
292
|
+
} catch {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/server.ts
|
|
299
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
300
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
301
|
+
import {
|
|
302
|
+
CallToolRequestSchema,
|
|
303
|
+
ListResourcesRequestSchema,
|
|
304
|
+
ListToolsRequestSchema,
|
|
305
|
+
ReadResourceRequestSchema
|
|
306
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
307
|
+
|
|
308
|
+
// src/tools/connect.ts
|
|
309
|
+
import { z } from "zod";
|
|
310
|
+
var connectSchema = z.object({
|
|
311
|
+
serverUrl: z.string().describe(
|
|
312
|
+
"The base URL of the i3X Gateway (e.g. 'http://192.168.1.50:8000' or 'https://i3x-portal.sterfive.io/instance/SESSION_ID')"
|
|
313
|
+
),
|
|
314
|
+
authToken: z.string().optional().describe("Optional Bearer authentication token for secured endpoints"),
|
|
315
|
+
verifyConnection: z.boolean().optional().default(true).describe("Whether to immediately verify connectivity by querying server /v1/info")
|
|
316
|
+
});
|
|
317
|
+
async function handleConnect(args, onBindingSwitch) {
|
|
318
|
+
const { serverUrl, authToken, verifyConnection } = args;
|
|
319
|
+
const newBinding = new RestClientBinding(serverUrl, authToken);
|
|
320
|
+
let serverInfo;
|
|
321
|
+
if (verifyConnection) {
|
|
322
|
+
try {
|
|
323
|
+
serverInfo = await newBinding.getInfo();
|
|
324
|
+
} catch (err) {
|
|
325
|
+
return {
|
|
326
|
+
status: "connection_failed",
|
|
327
|
+
serverUrl,
|
|
328
|
+
error: err.message,
|
|
329
|
+
tip: "Please check that the server URL is reachable and the authentication token (if required) is valid."
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
onBindingSwitch(newBinding);
|
|
334
|
+
return {
|
|
335
|
+
status: "connected",
|
|
336
|
+
serverUrl,
|
|
337
|
+
authenticated: Boolean(authToken),
|
|
338
|
+
serverInfo: serverInfo || "Connection established without verification.",
|
|
339
|
+
message: `Successfully connected to ${serverUrl}. All subsequent i3X tool calls will query this endpoint.`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/tools/discover-types.ts
|
|
344
|
+
import { z as z2 } from "zod";
|
|
345
|
+
var discoverTypesSchema = z2.object({
|
|
346
|
+
typeElementId: z2.string().optional().describe(
|
|
347
|
+
"Optional specific ObjectType elementId (e.g. 'BoilerType'). If omitted, lists all types."
|
|
348
|
+
),
|
|
349
|
+
namespaceUri: z2.string().optional().describe("Optional namespaceUri filter (e.g. 'http://sterfive.com/UA/Demo/')")
|
|
350
|
+
});
|
|
351
|
+
async function handleDiscoverTypes(binding, args) {
|
|
352
|
+
const { typeElementId, namespaceUri } = args;
|
|
353
|
+
const [objectTypes, relationshipTypes, namespaces] = await Promise.all([
|
|
354
|
+
binding.getObjectTypes(namespaceUri),
|
|
355
|
+
binding.getRelationshipTypes(),
|
|
356
|
+
binding.getNamespaces()
|
|
357
|
+
]);
|
|
358
|
+
if (typeElementId) {
|
|
359
|
+
const matched = objectTypes.find((t) => t.elementId === typeElementId);
|
|
360
|
+
if (!matched) {
|
|
361
|
+
return {
|
|
362
|
+
error: `ObjectType '${typeElementId}' not found.`,
|
|
363
|
+
availableTypes: objectTypes.map((t) => t.elementId)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
objectType: matched
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
namespacesCount: namespaces.length,
|
|
372
|
+
namespaces,
|
|
373
|
+
objectTypesCount: objectTypes.length,
|
|
374
|
+
objectTypes: objectTypes.map((t) => {
|
|
375
|
+
const schemaObj = t.schema && typeof t.schema === "object" ? t.schema : null;
|
|
376
|
+
const required = schemaObj && Array.isArray(schemaObj.required) ? schemaObj.required : [];
|
|
377
|
+
return {
|
|
378
|
+
elementId: t.elementId,
|
|
379
|
+
displayName: t.displayName || t.elementId,
|
|
380
|
+
namespaceUri: t.namespaceUri,
|
|
381
|
+
requiredProperties: required
|
|
382
|
+
};
|
|
383
|
+
}),
|
|
384
|
+
relationshipTypesCount: relationshipTypes.length,
|
|
385
|
+
relationshipTypes
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/tools/explore-hierarchy.ts
|
|
390
|
+
import { z as z3 } from "zod";
|
|
391
|
+
var exploreHierarchySchema = z3.object({
|
|
392
|
+
rootElementId: z3.string().optional().describe(
|
|
393
|
+
"Optional specific root asset elementId. If omitted, explores all root assets."
|
|
394
|
+
),
|
|
395
|
+
maxDepth: z3.number().optional().default(2).describe("Maximum depth to traverse child components (1 to 5, default: 2)")
|
|
396
|
+
});
|
|
397
|
+
async function handleExploreHierarchy(binding, args) {
|
|
398
|
+
const maxDepth = Math.min(Math.max(args.maxDepth || 2, 1), 5);
|
|
399
|
+
let roots;
|
|
400
|
+
if (args.rootElementId) {
|
|
401
|
+
const root = await binding.getObjectById(args.rootElementId);
|
|
402
|
+
if (!root) {
|
|
403
|
+
const suggestions = await binding.findSuggestions(args.rootElementId);
|
|
404
|
+
return {
|
|
405
|
+
error: `Root asset '${args.rootElementId}' not found.`,
|
|
406
|
+
suggestions
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
roots = [root];
|
|
410
|
+
} else {
|
|
411
|
+
roots = await binding.getObjects({ rootOnly: true });
|
|
412
|
+
}
|
|
413
|
+
async function buildNode(asset, currentDepth, visited) {
|
|
414
|
+
const node = {
|
|
415
|
+
elementId: asset.elementId,
|
|
416
|
+
displayName: asset.displayName || asset.elementId,
|
|
417
|
+
typeElementId: asset.typeElementId
|
|
418
|
+
};
|
|
419
|
+
visited.add(asset.elementId);
|
|
420
|
+
if (currentDepth < maxDepth) {
|
|
421
|
+
const children = await binding.getRelatedObjects(asset.elementId);
|
|
422
|
+
const unvisitedChildren = children.filter((c) => !visited.has(c.elementId));
|
|
423
|
+
if (unvisitedChildren.length > 0) {
|
|
424
|
+
node.children = await Promise.all(
|
|
425
|
+
unvisitedChildren.map(
|
|
426
|
+
(child) => buildNode(child, currentDepth + 1, new Set(visited))
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return node;
|
|
432
|
+
}
|
|
433
|
+
const hierarchyTree = await Promise.all(
|
|
434
|
+
roots.map((root) => buildNode(root, 1, /* @__PURE__ */ new Set()))
|
|
435
|
+
);
|
|
436
|
+
return {
|
|
437
|
+
totalRoots: roots.length,
|
|
438
|
+
traversalDepth: maxDepth,
|
|
439
|
+
plantTopology: hierarchyTree
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/tools/inspect-asset.ts
|
|
444
|
+
import { z as z4 } from "zod";
|
|
445
|
+
var inspectAssetSchema = z4.object({
|
|
446
|
+
elementId: z4.string().describe("The elementId of the asset to inspect (e.g. 'Pump_101', 'Boiler_101')"),
|
|
447
|
+
includeValues: z4.boolean().optional().default(true).describe("Whether to include live VQT (Value, Quality, Timestamp) telemetry values"),
|
|
448
|
+
includeSchema: z4.boolean().optional().default(true).describe("Whether to include the ObjectType JSON Schema definition")
|
|
449
|
+
});
|
|
450
|
+
async function handleInspectAsset(binding, args) {
|
|
451
|
+
const { elementId, includeValues, includeSchema } = args;
|
|
452
|
+
const asset = await binding.getObjectById(elementId);
|
|
453
|
+
if (!asset) {
|
|
454
|
+
const suggestions = await binding.findSuggestions(elementId);
|
|
455
|
+
return {
|
|
456
|
+
error: `Asset '${elementId}' not found in i3X address space.`,
|
|
457
|
+
suggestions: suggestions.length > 0 ? suggestions : "No close matches found.",
|
|
458
|
+
tip: "Use 'i3x_explore_plant_hierarchy' to list all root industrial assets."
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const result = {
|
|
462
|
+
elementId: asset.elementId,
|
|
463
|
+
displayName: asset.displayName || asset.elementId,
|
|
464
|
+
description: asset.metadata?.description || void 0,
|
|
465
|
+
typeElementId: asset.typeElementId,
|
|
466
|
+
isRoot: !asset.parentId
|
|
467
|
+
};
|
|
468
|
+
const relatedPromise = binding.getRelatedObjects(elementId);
|
|
469
|
+
const schemaPromise = includeSchema && asset.typeElementId ? binding.getObjectTypes() : Promise.resolve(null);
|
|
470
|
+
const valuesPromise = includeValues ? binding.getObjectValues([elementId]) : Promise.resolve(null);
|
|
471
|
+
const [relatedResult, schemaResult, valuesResult] = await Promise.allSettled([
|
|
472
|
+
relatedPromise,
|
|
473
|
+
schemaPromise,
|
|
474
|
+
valuesPromise
|
|
475
|
+
]);
|
|
476
|
+
if (relatedResult.status === "fulfilled" && Array.isArray(relatedResult.value) && relatedResult.value.length > 0) {
|
|
477
|
+
result.subComponents = relatedResult.value.map((r) => ({
|
|
478
|
+
elementId: r.elementId,
|
|
479
|
+
displayName: r.displayName || r.elementId,
|
|
480
|
+
typeElementId: r.typeElementId
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
if (schemaResult.status === "fulfilled" && Array.isArray(schemaResult.value)) {
|
|
484
|
+
const targetType = schemaResult.value.find(
|
|
485
|
+
(t) => t.elementId === asset.typeElementId
|
|
486
|
+
);
|
|
487
|
+
if (targetType) {
|
|
488
|
+
result.typeDefinition = {
|
|
489
|
+
elementId: targetType.elementId,
|
|
490
|
+
displayName: targetType.displayName,
|
|
491
|
+
schema: targetType.schema
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (valuesResult.status === "fulfilled" && Array.isArray(valuesResult.value) && valuesResult.value.length > 0) {
|
|
496
|
+
result.currentValues = valuesResult.value[0];
|
|
497
|
+
} else if (valuesResult.status === "rejected") {
|
|
498
|
+
result.currentValuesWarning = "Telemetry data temporarily unavailable";
|
|
499
|
+
}
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/tools/monitor-events.ts
|
|
504
|
+
import { z as z5 } from "zod";
|
|
505
|
+
var monitorEventsSchema = z5.object({
|
|
506
|
+
elementIds: z5.array(z5.string()).min(1).describe("Array of asset elementIds to monitor (e.g. ['Boiler_101', 'Pump_101'])"),
|
|
507
|
+
condition: z5.string().optional().describe(
|
|
508
|
+
"Optional monitoring alert condition filter (e.g. 'temperature > 85 || quality != Good')"
|
|
509
|
+
)
|
|
510
|
+
});
|
|
511
|
+
async function handleMonitorEvents(binding, args) {
|
|
512
|
+
const { elementIds, condition } = args;
|
|
513
|
+
const currentValues = await binding.getObjectValues(elementIds);
|
|
514
|
+
return {
|
|
515
|
+
status: "monitoring_active",
|
|
516
|
+
monitoredAssets: elementIds,
|
|
517
|
+
alertCondition: condition || "All value or quality changes",
|
|
518
|
+
initialSnapshot: currentValues,
|
|
519
|
+
instructions: "Subscription stream registered. The MCP server will emit notification events on matching VQT telemetry updates or threshold breaches."
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/tools/query-telemetry.ts
|
|
524
|
+
import { z as z6 } from "zod";
|
|
525
|
+
var queryTelemetrySchema = z6.object({
|
|
526
|
+
elementIds: z6.array(z6.string()).min(1).describe("Array of asset elementIds to query (e.g. ['Pump_101', 'Boiler_101'])"),
|
|
527
|
+
mode: z6.enum(["current", "history"]).optional().default("current").describe(
|
|
528
|
+
"Query mode: 'current' for live VQT telemetry, 'history' for time-series trend analysis"
|
|
529
|
+
),
|
|
530
|
+
startTime: z6.string().optional().describe(
|
|
531
|
+
"ISO 8601 start time or relative time string (e.g. '-2h', '-30m', '-1d') for history mode"
|
|
532
|
+
),
|
|
533
|
+
endTime: z6.string().optional().describe("ISO 8601 end time (defaults to now) for history mode"),
|
|
534
|
+
aggregateStats: z6.boolean().optional().default(true).describe(
|
|
535
|
+
"Whether to pre-compute min/max/mean statistical summaries for numeric sensor streams"
|
|
536
|
+
)
|
|
537
|
+
});
|
|
538
|
+
function parseStartTime(input) {
|
|
539
|
+
if (!input) {
|
|
540
|
+
const d = /* @__PURE__ */ new Date();
|
|
541
|
+
d.setHours(d.getHours() - 1);
|
|
542
|
+
return d.toISOString();
|
|
543
|
+
}
|
|
544
|
+
const match = input.match(/^-(\d+)([smhdw])$/i);
|
|
545
|
+
if (match) {
|
|
546
|
+
const amount = parseInt(match[1], 10);
|
|
547
|
+
const unit = match[2].toLowerCase();
|
|
548
|
+
const d = /* @__PURE__ */ new Date();
|
|
549
|
+
switch (unit) {
|
|
550
|
+
case "s":
|
|
551
|
+
d.setSeconds(d.getSeconds() - amount);
|
|
552
|
+
break;
|
|
553
|
+
case "m":
|
|
554
|
+
d.setMinutes(d.getMinutes() - amount);
|
|
555
|
+
break;
|
|
556
|
+
case "h":
|
|
557
|
+
d.setHours(d.getHours() - amount);
|
|
558
|
+
break;
|
|
559
|
+
case "d":
|
|
560
|
+
d.setDate(d.getDate() - amount);
|
|
561
|
+
break;
|
|
562
|
+
case "w":
|
|
563
|
+
d.setDate(d.getDate() - amount * 7);
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
return d.toISOString();
|
|
567
|
+
}
|
|
568
|
+
const parsed = new Date(input);
|
|
569
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
570
|
+
const defaultDate = /* @__PURE__ */ new Date();
|
|
571
|
+
defaultDate.setHours(defaultDate.getHours() - 1);
|
|
572
|
+
return defaultDate.toISOString();
|
|
573
|
+
}
|
|
574
|
+
return parsed.toISOString();
|
|
575
|
+
}
|
|
576
|
+
async function handleQueryTelemetry(binding, args) {
|
|
577
|
+
const { elementIds, mode, aggregateStats } = args;
|
|
578
|
+
if (mode === "current") {
|
|
579
|
+
const values = await binding.getObjectValues(elementIds);
|
|
580
|
+
return {
|
|
581
|
+
mode: "current",
|
|
582
|
+
telemetry: values
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
const startTime = parseStartTime(args.startTime);
|
|
586
|
+
const endTimeDate = args.endTime ? new Date(args.endTime) : /* @__PURE__ */ new Date();
|
|
587
|
+
const endTime = Number.isNaN(endTimeDate.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : endTimeDate.toISOString();
|
|
588
|
+
const history = await binding.getHistoricalValues(elementIds, startTime, endTime);
|
|
589
|
+
if (!aggregateStats) {
|
|
590
|
+
return {
|
|
591
|
+
mode: "history",
|
|
592
|
+
timeRange: { startTime, endTime },
|
|
593
|
+
history
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const reports = history.map((record, idx) => {
|
|
597
|
+
const elementId = elementIds[idx] || `element_${idx}`;
|
|
598
|
+
const report = {
|
|
599
|
+
elementId,
|
|
600
|
+
timeRange: { startTime, endTime },
|
|
601
|
+
dataPoints: record.values || []
|
|
602
|
+
};
|
|
603
|
+
const numericValues = [];
|
|
604
|
+
let badQualityCount = 0;
|
|
605
|
+
for (const v of record.values || []) {
|
|
606
|
+
if (v.quality && String(v.quality).toUpperCase() !== "GOOD") {
|
|
607
|
+
badQualityCount++;
|
|
608
|
+
}
|
|
609
|
+
if (typeof v.value === "number" && !Number.isNaN(v.value)) {
|
|
610
|
+
numericValues.push(v.value);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (numericValues.length > 0) {
|
|
614
|
+
const min = Math.min(...numericValues);
|
|
615
|
+
const max = Math.max(...numericValues);
|
|
616
|
+
const sum = numericValues.reduce((a, b) => a + b, 0);
|
|
617
|
+
const mean = Math.round(sum / numericValues.length * 100) / 100;
|
|
618
|
+
report.statistics = {
|
|
619
|
+
min,
|
|
620
|
+
max,
|
|
621
|
+
mean,
|
|
622
|
+
count: numericValues.length,
|
|
623
|
+
badQualityCount
|
|
624
|
+
};
|
|
625
|
+
} else {
|
|
626
|
+
report.statistics = {
|
|
627
|
+
count: (record.values || []).length,
|
|
628
|
+
badQualityCount
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return report;
|
|
632
|
+
});
|
|
633
|
+
return {
|
|
634
|
+
mode: "history",
|
|
635
|
+
timeRange: { startTime, endTime },
|
|
636
|
+
telemetryReports: reports
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/server.ts
|
|
641
|
+
var NodeI3xMcpServer = class {
|
|
642
|
+
server;
|
|
643
|
+
binding;
|
|
644
|
+
constructor(binding) {
|
|
645
|
+
this.binding = binding || new RestClientBinding();
|
|
646
|
+
this.server = new Server(
|
|
647
|
+
{
|
|
648
|
+
name: "@node-i3x/mcp",
|
|
649
|
+
version: "0.10.0"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
capabilities: {
|
|
653
|
+
tools: {},
|
|
654
|
+
resources: {}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
this.setupHandlers();
|
|
659
|
+
}
|
|
660
|
+
setBinding(binding) {
|
|
661
|
+
this.binding = binding;
|
|
662
|
+
}
|
|
663
|
+
getBinding() {
|
|
664
|
+
return this.binding;
|
|
665
|
+
}
|
|
666
|
+
setupHandlers() {
|
|
667
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
668
|
+
tools: [
|
|
669
|
+
{
|
|
670
|
+
name: "i3x_connect",
|
|
671
|
+
description: "Connect or switch dynamically to a specific i3X server endpoint URL and optional Bearer token at runtime.",
|
|
672
|
+
inputSchema: {
|
|
673
|
+
type: "object",
|
|
674
|
+
properties: {
|
|
675
|
+
serverUrl: {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "Target i3X Gateway REST endpoint URL (e.g. 'http://192.168.1.50:8000' or 'https://i3x-portal.sterfive.io/instance/SESSION_ID')"
|
|
678
|
+
},
|
|
679
|
+
authToken: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "Optional Bearer authentication token for secured endpoints"
|
|
682
|
+
},
|
|
683
|
+
verifyConnection: {
|
|
684
|
+
type: "boolean",
|
|
685
|
+
description: "Whether to verify connectivity by querying server /v1/info (default: true)"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
required: ["serverUrl"]
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
name: "i3x_inspect_asset",
|
|
693
|
+
description: "Single-turn complete asset diagnostic. Returns identity, ObjectType schema, parent/child hierarchy, and live VQT telemetry in 1 call.",
|
|
694
|
+
inputSchema: {
|
|
695
|
+
type: "object",
|
|
696
|
+
properties: {
|
|
697
|
+
elementId: {
|
|
698
|
+
type: "string",
|
|
699
|
+
description: "The elementId of the asset to inspect (e.g. 'Pump_101', 'Boiler_101')"
|
|
700
|
+
},
|
|
701
|
+
includeValues: {
|
|
702
|
+
type: "boolean",
|
|
703
|
+
description: "Whether to include live VQT telemetry values (default: true)"
|
|
704
|
+
},
|
|
705
|
+
includeSchema: {
|
|
706
|
+
type: "boolean",
|
|
707
|
+
description: "Whether to include the ObjectType JSON Schema definition (default: true)"
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
required: ["elementId"]
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "i3x_explore_plant_hierarchy",
|
|
715
|
+
description: "ISA-95 plant topology discovery. Traverses plant hierarchy from root assets down to child components up to maxDepth.",
|
|
716
|
+
inputSchema: {
|
|
717
|
+
type: "object",
|
|
718
|
+
properties: {
|
|
719
|
+
rootElementId: {
|
|
720
|
+
type: "string",
|
|
721
|
+
description: "Optional specific root asset elementId. If omitted, explores all root assets."
|
|
722
|
+
},
|
|
723
|
+
maxDepth: {
|
|
724
|
+
type: "number",
|
|
725
|
+
description: "Maximum depth to traverse child components (1 to 5, default: 2)"
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: "i3x_query_telemetry",
|
|
732
|
+
description: "Live current VQT telemetry or historical time-series trend analysis with pre-computed statistical summaries (min, max, mean).",
|
|
733
|
+
inputSchema: {
|
|
734
|
+
type: "object",
|
|
735
|
+
properties: {
|
|
736
|
+
elementIds: {
|
|
737
|
+
type: "array",
|
|
738
|
+
items: { type: "string" },
|
|
739
|
+
description: "Array of asset elementIds to query (e.g. ['Pump_101', 'Boiler_101'])"
|
|
740
|
+
},
|
|
741
|
+
mode: {
|
|
742
|
+
type: "string",
|
|
743
|
+
enum: ["current", "history"],
|
|
744
|
+
description: "Query mode: 'current' for live VQT, 'history' for time-series trend analysis"
|
|
745
|
+
},
|
|
746
|
+
startTime: {
|
|
747
|
+
type: "string",
|
|
748
|
+
description: "ISO 8601 or relative duration (e.g. '-2h', '-30m') for history mode"
|
|
749
|
+
},
|
|
750
|
+
endTime: {
|
|
751
|
+
type: "string",
|
|
752
|
+
description: "ISO 8601 end time (defaults to now) for history mode"
|
|
753
|
+
},
|
|
754
|
+
aggregateStats: {
|
|
755
|
+
type: "boolean",
|
|
756
|
+
description: "Whether to pre-compute min/max/mean statistical summaries (default: true)"
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
required: ["elementIds"]
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
name: "i3x_discover_types_and_schemas",
|
|
764
|
+
description: "Search registered i3X ObjectTypes, RelationshipTypes, and JSON Schema field validation requirements.",
|
|
765
|
+
inputSchema: {
|
|
766
|
+
type: "object",
|
|
767
|
+
properties: {
|
|
768
|
+
typeElementId: {
|
|
769
|
+
type: "string",
|
|
770
|
+
description: "Optional specific ObjectType elementId (e.g. 'BoilerType'). If omitted, lists all types."
|
|
771
|
+
},
|
|
772
|
+
namespaceUri: {
|
|
773
|
+
type: "string",
|
|
774
|
+
description: "Optional namespaceUri filter (e.g. 'http://sterfive.com/UA/Demo/')"
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: "i3x_monitor_asset_events",
|
|
781
|
+
description: "Create an i3X subscription stream to monitor real-time VQT value changes or threshold alert conditions.",
|
|
782
|
+
inputSchema: {
|
|
783
|
+
type: "object",
|
|
784
|
+
properties: {
|
|
785
|
+
elementIds: {
|
|
786
|
+
type: "array",
|
|
787
|
+
items: { type: "string" },
|
|
788
|
+
description: "Array of asset elementIds to monitor"
|
|
789
|
+
},
|
|
790
|
+
condition: {
|
|
791
|
+
type: "string",
|
|
792
|
+
description: "Optional alert condition filter (e.g. 'temperature > 85 || quality != Good')"
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
required: ["elementIds"]
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
]
|
|
799
|
+
}));
|
|
800
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
801
|
+
const { name, arguments: rawArgs } = request.params;
|
|
802
|
+
try {
|
|
803
|
+
let result;
|
|
804
|
+
switch (name) {
|
|
805
|
+
case "i3x_connect": {
|
|
806
|
+
const parsed = connectSchema.parse(rawArgs);
|
|
807
|
+
result = await handleConnect(parsed, (newBinding) => {
|
|
808
|
+
this.binding = newBinding;
|
|
809
|
+
});
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
case "i3x_inspect_asset": {
|
|
813
|
+
const parsed = inspectAssetSchema.parse(rawArgs);
|
|
814
|
+
result = await handleInspectAsset(this.binding, parsed);
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
case "i3x_explore_plant_hierarchy": {
|
|
818
|
+
const parsed = exploreHierarchySchema.parse(rawArgs);
|
|
819
|
+
result = await handleExploreHierarchy(this.binding, parsed);
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case "i3x_query_telemetry": {
|
|
823
|
+
const parsed = queryTelemetrySchema.parse(rawArgs);
|
|
824
|
+
result = await handleQueryTelemetry(this.binding, parsed);
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
case "i3x_discover_types_and_schemas": {
|
|
828
|
+
const parsed = discoverTypesSchema.parse(rawArgs);
|
|
829
|
+
result = await handleDiscoverTypes(this.binding, parsed);
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
case "i3x_monitor_asset_events": {
|
|
833
|
+
const parsed = monitorEventsSchema.parse(rawArgs);
|
|
834
|
+
result = await handleMonitorEvents(this.binding, parsed);
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
default:
|
|
838
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
content: [
|
|
842
|
+
{
|
|
843
|
+
type: "text",
|
|
844
|
+
text: JSON.stringify(result, null, 2)
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
};
|
|
848
|
+
} catch (err) {
|
|
849
|
+
return {
|
|
850
|
+
content: [
|
|
851
|
+
{
|
|
852
|
+
type: "text",
|
|
853
|
+
text: JSON.stringify(
|
|
854
|
+
{
|
|
855
|
+
error: err.message,
|
|
856
|
+
tip: "Check parameters or use 'i3x_explore_plant_hierarchy' to list available assets."
|
|
857
|
+
},
|
|
858
|
+
null,
|
|
859
|
+
2
|
|
860
|
+
)
|
|
861
|
+
}
|
|
862
|
+
],
|
|
863
|
+
isError: true
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
868
|
+
resources: [
|
|
869
|
+
{
|
|
870
|
+
uri: "i3x://info",
|
|
871
|
+
name: "i3X Server Metadata",
|
|
872
|
+
description: "Server capabilities, spec version, and vendor information",
|
|
873
|
+
mimeType: "application/json"
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
uri: "i3x://namespaces",
|
|
877
|
+
name: "Registered Namespaces",
|
|
878
|
+
description: "List of registered OPC UA / i3X namespace URIs",
|
|
879
|
+
mimeType: "application/json"
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
}));
|
|
883
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
884
|
+
const { uri } = request.params;
|
|
885
|
+
if (uri === "i3x://info") {
|
|
886
|
+
const info = await this.binding.getInfo();
|
|
887
|
+
return {
|
|
888
|
+
contents: [
|
|
889
|
+
{
|
|
890
|
+
uri,
|
|
891
|
+
mimeType: "application/json",
|
|
892
|
+
text: JSON.stringify(info, null, 2)
|
|
893
|
+
}
|
|
894
|
+
]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
if (uri === "i3x://namespaces") {
|
|
898
|
+
const namespaces = await this.binding.getNamespaces();
|
|
899
|
+
return {
|
|
900
|
+
contents: [
|
|
901
|
+
{
|
|
902
|
+
uri,
|
|
903
|
+
mimeType: "application/json",
|
|
904
|
+
text: JSON.stringify(namespaces, null, 2)
|
|
905
|
+
}
|
|
906
|
+
]
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
async startStdio() {
|
|
913
|
+
const transport = new StdioServerTransport();
|
|
914
|
+
await this.server.connect(transport);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
export {
|
|
918
|
+
InProcessConnectorBinding,
|
|
919
|
+
NodeI3xMcpServer,
|
|
920
|
+
RestClientBinding,
|
|
921
|
+
discoverTypesSchema,
|
|
922
|
+
exploreHierarchySchema,
|
|
923
|
+
handleDiscoverTypes,
|
|
924
|
+
handleExploreHierarchy,
|
|
925
|
+
handleInspectAsset,
|
|
926
|
+
handleMonitorEvents,
|
|
927
|
+
handleQueryTelemetry,
|
|
928
|
+
inspectAssetSchema,
|
|
929
|
+
monitorEventsSchema,
|
|
930
|
+
queryTelemetrySchema
|
|
931
|
+
};
|
|
932
|
+
//# sourceMappingURL=index.js.map
|