@yarkivaev/scada 2.3.85 → 2.3.87
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 +20 -0
- package/index.js +5 -0
- package/package.json +4 -2
- package/src/application/mcp/analyticsMcp.js +376 -0
- package/src/application/mcp/coverageCursor.js +116 -0
- package/src/application/mcp/mcpCoverage.js +95 -0
- package/src/application/mcp/mcpJournal.js +102 -0
- package/src/application/mcp/mcpMetrics.js +69 -0
- package/src/application/mcp/mcpObserve.js +168 -0
- package/src/application/mcp/mcpQuery.js +80 -0
- package/src/application/mcp/mcpReads.js +221 -0
- package/src/application/mcp/mcpResources.js +106 -0
- package/src/application/mcp/mcpText.js +106 -0
- package/src/application/metricsPlant.js +19 -1
- package/src/application/shopWithTimeline.js +15 -6
- package/src/domain/namespace/namespaceTree.js +182 -0
- package/src/domain/plant/plant.js +2 -1
- package/src/domain/span/coverageSpans.js +229 -0
- package/src/domain/sql/sqlGuard.js +175 -0
- package/src/domain/sql/sqlShape.js +16 -0
- package/src/infrastructure/persistence/pg/alerts.js +37 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import sqlShape from '../../domain/sql/sqlShape.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Caps a journal string.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text - raw text
|
|
7
|
+
* @returns {string} at most 512 characters
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* cap('SELECT 1');
|
|
11
|
+
*/
|
|
12
|
+
function cap(text) {
|
|
13
|
+
if (text.length > 512) {
|
|
14
|
+
return text.slice(0, 512);
|
|
15
|
+
}
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reads SQL from a query tool call.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} name - tool or resource name
|
|
23
|
+
* @param {object} [args] - tool arguments
|
|
24
|
+
* @returns {string} SQL or an empty string
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* sqlText('query_clickhouse', { sql: 'SELECT 1' });
|
|
28
|
+
*/
|
|
29
|
+
function sqlText(name, args) {
|
|
30
|
+
if (name !== 'query_clickhouse' && name !== 'query_postgres') {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
if (args && typeof args.sql === 'string') {
|
|
34
|
+
return args.sql;
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds the common journal fields.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} ctx - caller and writers
|
|
43
|
+
* @param {string} name - tool or resource name
|
|
44
|
+
* @param {string} status - ok or error
|
|
45
|
+
* @param {number} durationMs - elapsed milliseconds
|
|
46
|
+
* @returns {object} journal record
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* base({ caller: 'analyst' }, 'state', 'ok', 4);
|
|
50
|
+
*/
|
|
51
|
+
function base(ctx, name, status, durationMs) {
|
|
52
|
+
return {
|
|
53
|
+
caller: ctx.caller,
|
|
54
|
+
durationMs,
|
|
55
|
+
name,
|
|
56
|
+
status,
|
|
57
|
+
ts: new Date().toISOString()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Adds capped SQL text and a collapsed shape for query tools.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} record - journal record
|
|
65
|
+
* @param {string} name - tool or resource name
|
|
66
|
+
* @param {object} [args] - tool arguments
|
|
67
|
+
* @returns {object} record
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* withSql({ name: 'state' }, 'state');
|
|
71
|
+
*/
|
|
72
|
+
function withSql(record, name, args) {
|
|
73
|
+
const sql = sqlText(name, args);
|
|
74
|
+
if (sql.length === 0) {
|
|
75
|
+
return record;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...record,
|
|
79
|
+
shape: cap(sqlShape(sql)),
|
|
80
|
+
sql: cap(sql)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Writes one JSON line and counts the call.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} ctx - caller, journal, metrics
|
|
88
|
+
* @param {string} name - tool or resource name
|
|
89
|
+
* @param {string} status - ok or error
|
|
90
|
+
* @param {number} started - epoch milliseconds
|
|
91
|
+
* @param {object} [args] - tool arguments
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* note({ caller: 'analyst', journal() {}, metrics: { mark() {} } }, 'state', 'ok', Date.now());
|
|
96
|
+
*/
|
|
97
|
+
export default function note(ctx, name, status, started, args) {
|
|
98
|
+
const durationMs = Date.now() - started;
|
|
99
|
+
const record = base(ctx, name, status, durationMs);
|
|
100
|
+
ctx.metrics.mark(name, status);
|
|
101
|
+
ctx.journal(`${JSON.stringify(withSql(record, name, args))}\n`);
|
|
102
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes a Prometheus label value.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} value - raw label
|
|
5
|
+
* @returns {string} escaped label
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* escapeLabel('query_clickhouse');
|
|
9
|
+
*/
|
|
10
|
+
function escapeLabel(value) {
|
|
11
|
+
return String(value).replace(/\\/gu, '\\\\').replace(/"/gu, '\\"').replace(/\n/gu, '\\n');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders counters and gauges as Prometheus text.
|
|
16
|
+
*
|
|
17
|
+
* @param {Map<string, number>} calls - tool and status counts
|
|
18
|
+
* @param {object} inflight - counts by engine
|
|
19
|
+
* @returns {string} exposition
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* render(new Map(), { clickhouse: 0, plant: 0, postgres: 0 });
|
|
23
|
+
*/
|
|
24
|
+
function render(calls, inflight) {
|
|
25
|
+
const lines = ['# TYPE mcp_calls_total counter'];
|
|
26
|
+
for (const [key, count] of calls) {
|
|
27
|
+
const parts = key.split('\u0000');
|
|
28
|
+
lines.push(`mcp_calls_total{tool="${escapeLabel(parts[0])}",status="${escapeLabel(parts[1])}"} ${count}`);
|
|
29
|
+
}
|
|
30
|
+
lines.push('# TYPE mcp_inflight gauge');
|
|
31
|
+
lines.push(`mcp_inflight{engine="clickhouse"} ${inflight.clickhouse}`);
|
|
32
|
+
lines.push(`mcp_inflight{engine="plant"} ${inflight.plant}`);
|
|
33
|
+
lines.push(`mcp_inflight{engine="postgres"} ${inflight.postgres}`);
|
|
34
|
+
return `${lines.join('\n')}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Prometheus text for analytics MCP calls.
|
|
39
|
+
*
|
|
40
|
+
* Labels are tool and status on the counter, and engine on the in-flight gauge.
|
|
41
|
+
* Caller, machine, and SQL are not labels.
|
|
42
|
+
*
|
|
43
|
+
* @returns {object} begin, end, mark, and text
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const metrics = mcpMetrics();
|
|
47
|
+
* metrics.mark('state', 'ok');
|
|
48
|
+
* metrics.text();
|
|
49
|
+
*/
|
|
50
|
+
export default function mcpMetrics() {
|
|
51
|
+
const calls = new Map();
|
|
52
|
+
const inflight = { clickhouse: 0, plant: 0, postgres: 0 };
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
begin(engine) {
|
|
55
|
+
inflight[engine] += 1;
|
|
56
|
+
},
|
|
57
|
+
end(engine) {
|
|
58
|
+
inflight[engine] -= 1;
|
|
59
|
+
},
|
|
60
|
+
mark(tool, status) {
|
|
61
|
+
const key = `${tool}\u0000${status}`;
|
|
62
|
+
const count = calls.get(key) || 0;
|
|
63
|
+
calls.set(key, count + 1);
|
|
64
|
+
},
|
|
65
|
+
text() {
|
|
66
|
+
return render(calls, inflight);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import note from './mcpJournal.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps a tool name to the engine gauge label.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} name - tool name
|
|
7
|
+
* @returns {string} clickhouse, postgres, or plant
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* engineOf('query_clickhouse');
|
|
11
|
+
*/
|
|
12
|
+
function engineOf(name) {
|
|
13
|
+
if (name === 'query_clickhouse') {
|
|
14
|
+
return 'clickhouse';
|
|
15
|
+
}
|
|
16
|
+
if (name === 'query_postgres') {
|
|
17
|
+
return 'postgres';
|
|
18
|
+
}
|
|
19
|
+
return 'plant';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads ok or error from a tool result.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} result - tool result
|
|
26
|
+
* @returns {string} status
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* statusOf({ isError: true });
|
|
30
|
+
*/
|
|
31
|
+
function statusOf(result) {
|
|
32
|
+
if (result && result.isError === true) {
|
|
33
|
+
return 'error';
|
|
34
|
+
}
|
|
35
|
+
return 'ok';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Runs a callback and keeps the status and any thrown error.
|
|
40
|
+
*
|
|
41
|
+
* @param {Function} cb - tool or resource callback
|
|
42
|
+
* @param {object} args - first callback argument
|
|
43
|
+
* @param {object} extra - SDK extra
|
|
44
|
+
* @returns {Promise<object>} outcome
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* invoke(() => ({ content: [] }), {}, {});
|
|
48
|
+
*/
|
|
49
|
+
async function invoke(cb, args, extra) {
|
|
50
|
+
try {
|
|
51
|
+
const result = await cb(args, extra);
|
|
52
|
+
return { result, status: statusOf(result) };
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return { err, status: 'error' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the callback result or rethrows its error.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} outcome - invoke outcome
|
|
62
|
+
* @returns {object} result
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* settle({ result: { content: [] }, status: 'ok' });
|
|
66
|
+
*/
|
|
67
|
+
function settle(outcome) {
|
|
68
|
+
if (outcome.err) {
|
|
69
|
+
throw outcome.err;
|
|
70
|
+
}
|
|
71
|
+
return outcome.result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Times one tool call, counts it, and tracks the engine gauge.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} name - tool name
|
|
78
|
+
* @param {object} args - tool arguments
|
|
79
|
+
* @param {object} extra - SDK extra
|
|
80
|
+
* @param {Function} cb - tool callback
|
|
81
|
+
* @param {object} ctx - journal context
|
|
82
|
+
* @returns {Promise<object>} tool result
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* trackedTool('state', {}, {}, () => ({ content: [] }), ctx);
|
|
86
|
+
*/
|
|
87
|
+
async function trackedTool(name, args, extra, cb, ctx) {
|
|
88
|
+
const engine = engineOf(name);
|
|
89
|
+
const started = Date.now();
|
|
90
|
+
ctx.metrics.begin(engine);
|
|
91
|
+
const outcome = await invoke(cb, args, extra);
|
|
92
|
+
ctx.metrics.end(engine);
|
|
93
|
+
note(ctx, name, outcome.status, started, args);
|
|
94
|
+
return settle(outcome);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Times one resource read.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} uri - resource URI
|
|
101
|
+
* @param {object} target - SDK uri argument
|
|
102
|
+
* @param {object} extra - SDK extra
|
|
103
|
+
* @param {Function} cb - read callback
|
|
104
|
+
* @param {object} ctx - journal context
|
|
105
|
+
* @returns {Promise<object>} resource result
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* trackedRead('plant://topology', new URL('plant://topology'), {}, () => ({}), ctx);
|
|
109
|
+
*/
|
|
110
|
+
async function trackedRead(uri, target, extra, cb, ctx) {
|
|
111
|
+
const started = Date.now();
|
|
112
|
+
const outcome = await invoke(cb, target, extra);
|
|
113
|
+
note(ctx, String(uri), outcome.status, started);
|
|
114
|
+
return settle(outcome);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wraps registerTool so every tools/call is journaled.
|
|
119
|
+
*
|
|
120
|
+
* @param {Function} registerTool - McpServer.registerTool
|
|
121
|
+
* @param {object} ctx - journal context
|
|
122
|
+
* @returns {Function} wrapped register
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* wrapTool(server.registerTool, ctx);
|
|
126
|
+
*/
|
|
127
|
+
function wrapTool(registerTool, ctx) {
|
|
128
|
+
return function wrap(name, config, cb) {
|
|
129
|
+
return registerTool(name, config, (args, extra) => {
|
|
130
|
+
return trackedTool(name, args, extra, cb, ctx);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Wraps registerResource so every resources/read is journaled.
|
|
137
|
+
*
|
|
138
|
+
* @param {Function} registerResource - McpServer.registerResource
|
|
139
|
+
* @param {object} ctx - journal context
|
|
140
|
+
* @returns {Function} wrapped register
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* wrapResource(server.registerResource, ctx);
|
|
144
|
+
*/
|
|
145
|
+
function wrapResource(registerResource, ctx) {
|
|
146
|
+
return function wrap(name, uri, config, cb) {
|
|
147
|
+
return registerResource(name, uri, config, (target, extra) => {
|
|
148
|
+
return trackedRead(uri, target, extra, cb, ctx);
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Journals later tool and resource registrations on this server.
|
|
155
|
+
*
|
|
156
|
+
* @param {object} server - McpServer
|
|
157
|
+
* @param {object} ctx - caller, journal, metrics
|
|
158
|
+
* @returns {void}
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* observe(server, { caller: 'analyst', journal() {}, metrics });
|
|
162
|
+
*/
|
|
163
|
+
export default function observe(server, ctx) {
|
|
164
|
+
const registerResource = server.registerResource.bind(server);
|
|
165
|
+
const registerTool = server.registerTool.bind(server);
|
|
166
|
+
server.registerResource = wrapResource(registerResource, ctx);
|
|
167
|
+
server.registerTool = wrapTool(registerTool, ctx);
|
|
168
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import sqlGuard from '../../domain/sql/sqlGuard.js';
|
|
3
|
+
import { capRows, toolText } from './mcpText.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Guards SQL, runs the port, and caps rows.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} sql - statement
|
|
9
|
+
* @param {string} engine - clickhouse or postgres
|
|
10
|
+
* @param {Function} query - port
|
|
11
|
+
* @returns {Promise<object>} tool result
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* runQuery('SELECT 1', 'postgres', () => []);
|
|
15
|
+
*/
|
|
16
|
+
async function runQuery(sql, engine, query) {
|
|
17
|
+
const guarded = sqlGuard(sql, engine);
|
|
18
|
+
const rows = await query(guarded);
|
|
19
|
+
return toolText(capRows(rows));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers read-only ClickHouse SQL.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} server - McpServer
|
|
26
|
+
* @param {object} ports - read ports
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* registerClickhouse(server, ports);
|
|
31
|
+
*/
|
|
32
|
+
function registerClickhouse(server, ports) {
|
|
33
|
+
server.registerTool('query_clickhouse', {
|
|
34
|
+
description: 'Read-only ClickHouse SQL with a ts predicate',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
sql: z.string()
|
|
37
|
+
}
|
|
38
|
+
}, (args) => {
|
|
39
|
+
return runQuery(args.sql, 'clickhouse', ports.queryClickhouse);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Registers read-only Postgres SQL when a standby port exists.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} server - McpServer
|
|
47
|
+
* @param {object} ports - read ports
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* registerPostgres(server, ports);
|
|
52
|
+
*/
|
|
53
|
+
function registerPostgres(server, ports) {
|
|
54
|
+
if (typeof ports.queryPostgres !== 'function') {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
server.registerTool('query_postgres', {
|
|
58
|
+
description: 'Read-only Postgres SQL',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
sql: z.string()
|
|
61
|
+
}
|
|
62
|
+
}, (args) => {
|
|
63
|
+
return runQuery(args.sql, 'postgres', ports.queryPostgres);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Registers SQL tools. Postgres is omitted without a standby port.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} server - McpServer
|
|
71
|
+
* @param {object} ports - read ports
|
|
72
|
+
* @returns {void}
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* registerQueries(server, ports);
|
|
76
|
+
*/
|
|
77
|
+
export default function registerQueries(server, ports) {
|
|
78
|
+
registerClickhouse(server, ports);
|
|
79
|
+
registerPostgres(server, ports);
|
|
80
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { capMeasurements, capRows, toolText } from './mcpText.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Loads rows for one namespace path and caps them.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} tree - namespaceTree
|
|
8
|
+
* @param {string} path - namespace path
|
|
9
|
+
* @param {Function} load - port call
|
|
10
|
+
* @returns {Promise<object>} tool result
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* loadRows(tree, 'site', () => []);
|
|
14
|
+
*/
|
|
15
|
+
async function loadRows(tree, path, load) {
|
|
16
|
+
tree.resolve(path);
|
|
17
|
+
const rows = await load();
|
|
18
|
+
return toolText(capRows(rows));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adds age and live or stale from the node silence threshold.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} raw - state port payload
|
|
25
|
+
* @param {number} silenceSeconds - liveness seconds
|
|
26
|
+
* @returns {object} state payload
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* fresh({ found: false }, 60);
|
|
30
|
+
*/
|
|
31
|
+
function fresh(raw, silenceSeconds) {
|
|
32
|
+
if (raw && raw.found === true && typeof raw.time === 'string') {
|
|
33
|
+
const ageMs = Date.now() - Date.parse(raw.time);
|
|
34
|
+
const freshness = ageMs <= silenceSeconds * 1000 ? 'live' : 'stale';
|
|
35
|
+
return { ...raw, ageMs, freshness };
|
|
36
|
+
}
|
|
37
|
+
return raw;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reads metric points for namespace paths.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} args - paths, from, to, step, mode
|
|
44
|
+
* @param {object} ports - read ports
|
|
45
|
+
* @param {object} tree - namespaceTree
|
|
46
|
+
* @returns {Promise<object>} tool result
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* runMeasurements({ paths: ['site'], from: 'a', to: 'b', step: 60 }, ports, tree);
|
|
50
|
+
*/
|
|
51
|
+
async function runMeasurements(args, ports, tree) {
|
|
52
|
+
for (const path of args.paths) {
|
|
53
|
+
tree.resolve(path);
|
|
54
|
+
}
|
|
55
|
+
const raw = await ports.measurements(args);
|
|
56
|
+
return toolText(capMeasurements(raw, args.step));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reads the latest values under a path.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} args - path
|
|
63
|
+
* @param {object} ports - read ports
|
|
64
|
+
* @param {object} tree - namespaceTree
|
|
65
|
+
* @returns {Promise<object>} tool result
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* runState({ path: 'site' }, ports, tree);
|
|
69
|
+
*/
|
|
70
|
+
async function runState(args, ports, tree) {
|
|
71
|
+
const node = tree.resolve(args.path);
|
|
72
|
+
const raw = await ports.state(args);
|
|
73
|
+
return toolText(fresh(raw, node.silenceSeconds));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Registers the measurements tool.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} server - McpServer
|
|
80
|
+
* @param {object} ports - read ports
|
|
81
|
+
* @param {object} tree - namespaceTree
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* registerMeasurements(server, ports, tree);
|
|
86
|
+
*/
|
|
87
|
+
function registerMeasurements(server, ports, tree) {
|
|
88
|
+
server.registerTool('measurements', {
|
|
89
|
+
description: 'Metric points for namespace paths over an interval',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
from: z.string(),
|
|
92
|
+
mode: z.string().optional(),
|
|
93
|
+
paths: z.array(z.string()),
|
|
94
|
+
step: z.number(),
|
|
95
|
+
to: z.string()
|
|
96
|
+
}
|
|
97
|
+
}, (args) => {
|
|
98
|
+
return runMeasurements(args, ports, tree);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Registers the segments tool.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} server - McpServer
|
|
106
|
+
* @param {object} ports - read ports
|
|
107
|
+
* @param {object} tree - namespaceTree
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* registerSegments(server, ports, tree);
|
|
112
|
+
*/
|
|
113
|
+
function registerSegments(server, ports, tree) {
|
|
114
|
+
server.registerTool('segments', {
|
|
115
|
+
description: 'Timeline segments for a namespace path',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
from: z.string(),
|
|
118
|
+
kinds: z.array(z.string()).optional(),
|
|
119
|
+
path: z.string(),
|
|
120
|
+
to: z.string()
|
|
121
|
+
}
|
|
122
|
+
}, (args) => {
|
|
123
|
+
return loadRows(tree, args.path, () => {
|
|
124
|
+
return ports.segments(args);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Registers the operations tool.
|
|
131
|
+
*
|
|
132
|
+
* @param {object} server - McpServer
|
|
133
|
+
* @param {object} ports - read ports
|
|
134
|
+
* @param {object} tree - namespaceTree
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* registerOperations(server, ports, tree);
|
|
139
|
+
*/
|
|
140
|
+
function registerOperations(server, ports, tree) {
|
|
141
|
+
server.registerTool('operations', {
|
|
142
|
+
description: 'Operation rows for a namespace path and an opaque kind',
|
|
143
|
+
inputSchema: {
|
|
144
|
+
from: z.string(),
|
|
145
|
+
kind: z.string(),
|
|
146
|
+
path: z.string(),
|
|
147
|
+
to: z.string()
|
|
148
|
+
}
|
|
149
|
+
}, (args) => {
|
|
150
|
+
return loadRows(tree, args.path, () => {
|
|
151
|
+
return ports.operations(args);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Registers the alerts tool.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} server - McpServer
|
|
160
|
+
* @param {object} ports - read ports
|
|
161
|
+
* @param {object} tree - namespaceTree
|
|
162
|
+
* @returns {void}
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* registerAlerts(server, ports, tree);
|
|
166
|
+
*/
|
|
167
|
+
function registerAlerts(server, ports, tree) {
|
|
168
|
+
server.registerTool('alerts', {
|
|
169
|
+
description: 'Alert rows for a namespace path',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
from: z.string(),
|
|
172
|
+
path: z.string(),
|
|
173
|
+
to: z.string()
|
|
174
|
+
}
|
|
175
|
+
}, (args) => {
|
|
176
|
+
return loadRows(tree, args.path, () => {
|
|
177
|
+
return ports.alerts(args);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Registers the state tool.
|
|
184
|
+
*
|
|
185
|
+
* @param {object} server - McpServer
|
|
186
|
+
* @param {object} ports - read ports
|
|
187
|
+
* @param {object} tree - namespaceTree
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* registerState(server, ports, tree);
|
|
192
|
+
*/
|
|
193
|
+
function registerState(server, ports, tree) {
|
|
194
|
+
server.registerTool('state', {
|
|
195
|
+
description: 'Latest readings under a namespace path',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
path: z.string()
|
|
198
|
+
}
|
|
199
|
+
}, (args) => {
|
|
200
|
+
return runState(args, ports, tree);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Registers measurement, segment, operation, alert, and state tools.
|
|
206
|
+
*
|
|
207
|
+
* @param {object} server - McpServer
|
|
208
|
+
* @param {object} ports - read ports
|
|
209
|
+
* @param {object} tree - namespaceTree
|
|
210
|
+
* @returns {void}
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* registerReads(server, ports, tree);
|
|
214
|
+
*/
|
|
215
|
+
export default function registerReads(server, ports, tree) {
|
|
216
|
+
registerMeasurements(server, ports, tree);
|
|
217
|
+
registerSegments(server, ports, tree);
|
|
218
|
+
registerOperations(server, ports, tree);
|
|
219
|
+
registerAlerts(server, ports, tree);
|
|
220
|
+
registerState(server, ports, tree);
|
|
221
|
+
}
|