@burdenoff/mcp-libs 2026.627.3 → 2026.627.5
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/dist/modules/cosmicintersection/index.cjs +1984 -0
- package/dist/modules/cosmicintersection/index.cjs.map +1 -0
- package/dist/modules/cosmicintersection/index.d.cts +8 -0
- package/dist/modules/cosmicintersection/index.d.ts +8 -0
- package/dist/modules/cosmicintersection/index.js +1923 -0
- package/dist/modules/cosmicintersection/index.js.map +1 -0
- package/dist/modules/ecoimpacthub/index.cjs +2042 -0
- package/dist/modules/ecoimpacthub/index.cjs.map +1 -0
- package/dist/modules/ecoimpacthub/index.d.cts +8 -0
- package/dist/modules/ecoimpacthub/index.d.ts +8 -0
- package/dist/modules/ecoimpacthub/index.js +1981 -0
- package/dist/modules/ecoimpacthub/index.js.map +1 -0
- package/dist/modules/govcitizenhub/index.cjs +1686 -0
- package/dist/modules/govcitizenhub/index.cjs.map +1 -0
- package/dist/modules/govcitizenhub/index.d.cts +8 -0
- package/dist/modules/govcitizenhub/index.d.ts +8 -0
- package/dist/modules/govcitizenhub/index.js +1625 -0
- package/dist/modules/govcitizenhub/index.js.map +1 -0
- package/dist/modules/intelwatchtower/index.cjs +1919 -0
- package/dist/modules/intelwatchtower/index.cjs.map +1 -0
- package/dist/modules/intelwatchtower/index.d.cts +8 -0
- package/dist/modules/intelwatchtower/index.d.ts +8 -0
- package/dist/modules/intelwatchtower/index.js +1858 -0
- package/dist/modules/intelwatchtower/index.js.map +1 -0
- package/dist/modules/manufacturedops/index.cjs +1884 -0
- package/dist/modules/manufacturedops/index.cjs.map +1 -0
- package/dist/modules/manufacturedops/index.d.cts +8 -0
- package/dist/modules/manufacturedops/index.d.ts +8 -0
- package/dist/modules/manufacturedops/index.js +1823 -0
- package/dist/modules/manufacturedops/index.js.map +1 -0
- package/dist/modules/mybodyshield/index.cjs +1834 -0
- package/dist/modules/mybodyshield/index.cjs.map +1 -0
- package/dist/modules/mybodyshield/index.d.cts +8 -0
- package/dist/modules/mybodyshield/index.d.ts +8 -0
- package/dist/modules/mybodyshield/index.js +1773 -0
- package/dist/modules/mybodyshield/index.js.map +1 -0
- package/dist/modules/semanticfed/index.cjs +1846 -0
- package/dist/modules/semanticfed/index.cjs.map +1 -0
- package/dist/modules/semanticfed/index.d.cts +8 -0
- package/dist/modules/semanticfed/index.d.ts +8 -0
- package/dist/modules/semanticfed/index.js +1785 -0
- package/dist/modules/semanticfed/index.js.map +1 -0
- package/dist/modules/technospam/index.cjs +1962 -0
- package/dist/modules/technospam/index.cjs.map +1 -0
- package/dist/modules/technospam/index.d.cts +8 -0
- package/dist/modules/technospam/index.d.ts +8 -0
- package/dist/modules/technospam/index.js +1901 -0
- package/dist/modules/technospam/index.js.map +1 -0
- package/dist/modules/theculturepost/index.cjs +1587 -0
- package/dist/modules/theculturepost/index.cjs.map +1 -0
- package/dist/modules/theculturepost/index.d.cts +8 -0
- package/dist/modules/theculturepost/index.d.ts +8 -0
- package/dist/modules/theculturepost/index.js +1526 -0
- package/dist/modules/theculturepost/index.js.map +1 -0
- package/dist/modules/theglobalfuel/index.cjs +1678 -0
- package/dist/modules/theglobalfuel/index.cjs.map +1 -0
- package/dist/modules/theglobalfuel/index.d.cts +8 -0
- package/dist/modules/theglobalfuel/index.d.ts +8 -0
- package/dist/modules/theglobalfuel/index.js +1617 -0
- package/dist/modules/theglobalfuel/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1846 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
require('graphql-request');
|
|
5
|
+
require('cross-fetch');
|
|
6
|
+
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
+
require('@modelcontextprotocol/sdk/types.js');
|
|
8
|
+
|
|
9
|
+
// src/modules/semanticfed/tools/connectors.ts
|
|
10
|
+
|
|
11
|
+
// src/core/errors.ts
|
|
12
|
+
var McpInternalError = class extends Error {
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "McpInternalError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/core/result.ts
|
|
22
|
+
function jsonResult(data, structured) {
|
|
23
|
+
const result = {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
25
|
+
};
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function errorResult(message, hint) {
|
|
29
|
+
const text = hint ? `${message}
|
|
30
|
+
|
|
31
|
+
Hint: ${hint}` : message;
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/tool-name.ts
|
|
36
|
+
var MAX_TOOL_NAME_LENGTH = 63;
|
|
37
|
+
function assertSafeName(name, kind) {
|
|
38
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
39
|
+
throw new McpInternalError(
|
|
40
|
+
`Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function mcpToolName(productName, ...parts) {
|
|
46
|
+
if (!productName) {
|
|
47
|
+
throw new McpInternalError("mcpToolName: productName is required");
|
|
48
|
+
}
|
|
49
|
+
const name = [productName, ...parts].filter(Boolean).join("_");
|
|
50
|
+
return assertSafeName(name);
|
|
51
|
+
}
|
|
52
|
+
function mcpResourceUri(productName, ...parts) {
|
|
53
|
+
if (!productName) {
|
|
54
|
+
throw new McpInternalError("mcpResourceUri: productName is required");
|
|
55
|
+
}
|
|
56
|
+
return `${productName}://${parts.filter(Boolean).join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function mcpPromptName(productName, ...parts) {
|
|
59
|
+
if (!productName) {
|
|
60
|
+
throw new McpInternalError("mcpPromptName: productName is required");
|
|
61
|
+
}
|
|
62
|
+
return [productName, ...parts].filter(Boolean).join("-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/modules/semanticfed/operations.ts
|
|
66
|
+
var CONNECTOR_FIELDS = `
|
|
67
|
+
id name description kind isActive capabilities workspaceId createdAt updatedAt
|
|
68
|
+
`;
|
|
69
|
+
var CONNECTION_FIELDS = `
|
|
70
|
+
id dataSourceId health latencyMs message probedAt workspaceId createdAt updatedAt
|
|
71
|
+
`;
|
|
72
|
+
var DATA_SOURCE_FIELDS = `
|
|
73
|
+
id connectorId name description endpoint status pushdownEnabled secretRef tags
|
|
74
|
+
lastIntrospectedAt workspaceId createdAt updatedAt
|
|
75
|
+
`;
|
|
76
|
+
var DIMENSION_FIELDS = `
|
|
77
|
+
id entityId name description dataType expression isPrimary workspaceId createdAt updatedAt
|
|
78
|
+
`;
|
|
79
|
+
var METRIC_FIELDS = `
|
|
80
|
+
id entityId name description aggregation expression format workspaceId createdAt updatedAt
|
|
81
|
+
`;
|
|
82
|
+
var ENTITY_FIELDS = `
|
|
83
|
+
id modelId dataSourceId name description kind physicalName primaryKey sqlDefinition
|
|
84
|
+
workspaceId createdAt updatedAt
|
|
85
|
+
`;
|
|
86
|
+
var RELATIONSHIP_FIELDS = `
|
|
87
|
+
id modelId name description kind fromEntityId fromField toEntityId toField
|
|
88
|
+
workspaceId createdAt updatedAt
|
|
89
|
+
`;
|
|
90
|
+
var MODEL_FIELDS = `
|
|
91
|
+
id name description status version tags publishedAt workspaceId createdAt updatedAt
|
|
92
|
+
`;
|
|
93
|
+
var QUERY_FIELDS = `
|
|
94
|
+
id modelId name description queryText parameters status cacheable cacheTtlSeconds
|
|
95
|
+
tags workspaceId createdAt updatedAt
|
|
96
|
+
`;
|
|
97
|
+
var QUERY_RUN_FIELDS = `
|
|
98
|
+
id queryId status fromCache rowCount sourcesTouched durationMs planSummary
|
|
99
|
+
errorMessage runById startedAt finishedAt workspaceId createdAt updatedAt
|
|
100
|
+
`;
|
|
101
|
+
var ACCESS_POLICY_FIELDS = `
|
|
102
|
+
id name description modelId entityId principal effect scope rule isActive
|
|
103
|
+
workspaceId createdAt updatedAt
|
|
104
|
+
`;
|
|
105
|
+
var PAGE_INFO = `pageInfo { cursor hasMore }`;
|
|
106
|
+
var GET_CONNECTOR = `
|
|
107
|
+
query SemGetConnector($id: ID!) {
|
|
108
|
+
semConnector(id: $id) {
|
|
109
|
+
${CONNECTOR_FIELDS}
|
|
110
|
+
dataSources { ${DATA_SOURCE_FIELDS} }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
var LIST_CONNECTORS = `
|
|
115
|
+
query SemListConnectors($kind: SemConnectorKind, $pagination: PaginationInput) {
|
|
116
|
+
semListConnectors(kind: $kind, pagination: $pagination) {
|
|
117
|
+
items { ${CONNECTOR_FIELDS} }
|
|
118
|
+
${PAGE_INFO}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
var CREATE_CONNECTOR = `
|
|
123
|
+
mutation SemCreateConnector($input: SemCreateConnectorInput!) {
|
|
124
|
+
semCreateConnector(input: $input) {
|
|
125
|
+
${CONNECTOR_FIELDS}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
var UPDATE_CONNECTOR = `
|
|
130
|
+
mutation SemUpdateConnector($id: ID!, $input: SemUpdateConnectorInput!) {
|
|
131
|
+
semUpdateConnector(id: $id, input: $input) {
|
|
132
|
+
${CONNECTOR_FIELDS}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
var ARCHIVE_CONNECTOR = `
|
|
137
|
+
mutation SemArchiveConnector($id: ID!) {
|
|
138
|
+
semArchiveConnector(id: $id)
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
var GET_DATA_SOURCE = `
|
|
142
|
+
query SemGetDataSource($id: ID!) {
|
|
143
|
+
semDataSource(id: $id) {
|
|
144
|
+
${DATA_SOURCE_FIELDS}
|
|
145
|
+
connector { ${CONNECTOR_FIELDS} }
|
|
146
|
+
connections { ${CONNECTION_FIELDS} }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
var LIST_DATA_SOURCES = `
|
|
151
|
+
query SemListDataSources(
|
|
152
|
+
$connectorId: ID
|
|
153
|
+
$status: SemDataSourceStatus
|
|
154
|
+
$pagination: PaginationInput
|
|
155
|
+
) {
|
|
156
|
+
semListDataSources(connectorId: $connectorId, status: $status, pagination: $pagination) {
|
|
157
|
+
items { ${DATA_SOURCE_FIELDS} }
|
|
158
|
+
${PAGE_INFO}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
var CREATE_DATA_SOURCE = `
|
|
163
|
+
mutation SemCreateDataSource($input: SemCreateDataSourceInput!) {
|
|
164
|
+
semCreateDataSource(input: $input) {
|
|
165
|
+
${DATA_SOURCE_FIELDS}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
`;
|
|
169
|
+
var UPDATE_DATA_SOURCE = `
|
|
170
|
+
mutation SemUpdateDataSource($id: ID!, $input: SemUpdateDataSourceInput!) {
|
|
171
|
+
semUpdateDataSource(id: $id, input: $input) {
|
|
172
|
+
${DATA_SOURCE_FIELDS}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
var TRANSITION_DATA_SOURCE = `
|
|
177
|
+
mutation SemTransitionDataSource($id: ID!, $status: SemDataSourceStatus!) {
|
|
178
|
+
semTransitionDataSource(id: $id, status: $status) {
|
|
179
|
+
${DATA_SOURCE_FIELDS}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
var ARCHIVE_DATA_SOURCE = `
|
|
184
|
+
mutation SemArchiveDataSource($id: ID!) {
|
|
185
|
+
semArchiveDataSource(id: $id)
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
var LIST_CONNECTIONS = `
|
|
189
|
+
query SemListConnections($dataSourceId: ID!) {
|
|
190
|
+
semListConnections(dataSourceId: $dataSourceId) {
|
|
191
|
+
${CONNECTION_FIELDS}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
var RECORD_CONNECTION = `
|
|
196
|
+
mutation SemRecordConnection($input: SemRecordConnectionInput!) {
|
|
197
|
+
semRecordConnection(input: $input) {
|
|
198
|
+
${CONNECTION_FIELDS}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
var GET_MODEL = `
|
|
203
|
+
query SemGetModel($id: ID!) {
|
|
204
|
+
semModel(id: $id) {
|
|
205
|
+
${MODEL_FIELDS}
|
|
206
|
+
entities { ${ENTITY_FIELDS} }
|
|
207
|
+
relationships { ${RELATIONSHIP_FIELDS} }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
var LIST_MODELS = `
|
|
212
|
+
query SemListModels($status: SemModelStatus, $pagination: PaginationInput) {
|
|
213
|
+
semListModels(status: $status, pagination: $pagination) {
|
|
214
|
+
items { ${MODEL_FIELDS} }
|
|
215
|
+
${PAGE_INFO}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
var CREATE_MODEL = `
|
|
220
|
+
mutation SemCreateModel($input: SemCreateModelInput!) {
|
|
221
|
+
semCreateModel(input: $input) {
|
|
222
|
+
${MODEL_FIELDS}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
var UPDATE_MODEL = `
|
|
227
|
+
mutation SemUpdateModel($id: ID!, $input: SemUpdateModelInput!) {
|
|
228
|
+
semUpdateModel(id: $id, input: $input) {
|
|
229
|
+
${MODEL_FIELDS}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
var PUBLISH_MODEL = `
|
|
234
|
+
mutation SemPublishModel($id: ID!) {
|
|
235
|
+
semPublishModel(id: $id) {
|
|
236
|
+
${MODEL_FIELDS}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
var ARCHIVE_MODEL = `
|
|
241
|
+
mutation SemArchiveModel($id: ID!) {
|
|
242
|
+
semArchiveModel(id: $id)
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
var GET_ENTITY = `
|
|
246
|
+
query SemGetEntity($id: ID!) {
|
|
247
|
+
semEntity(id: $id) {
|
|
248
|
+
${ENTITY_FIELDS}
|
|
249
|
+
dimensions { ${DIMENSION_FIELDS} }
|
|
250
|
+
metrics { ${METRIC_FIELDS} }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
var LIST_ENTITIES = `
|
|
255
|
+
query SemListEntities($modelId: ID!) {
|
|
256
|
+
semListEntities(modelId: $modelId) {
|
|
257
|
+
${ENTITY_FIELDS}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
var CREATE_ENTITY = `
|
|
262
|
+
mutation SemCreateEntity($input: SemCreateEntityInput!) {
|
|
263
|
+
semCreateEntity(input: $input) {
|
|
264
|
+
${ENTITY_FIELDS}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
`;
|
|
268
|
+
var UPDATE_ENTITY = `
|
|
269
|
+
mutation SemUpdateEntity($id: ID!, $input: SemUpdateEntityInput!) {
|
|
270
|
+
semUpdateEntity(id: $id, input: $input) {
|
|
271
|
+
${ENTITY_FIELDS}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
var DELETE_ENTITY = `
|
|
276
|
+
mutation SemDeleteEntity($id: ID!) {
|
|
277
|
+
semDeleteEntity(id: $id)
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
var CREATE_DIMENSION = `
|
|
281
|
+
mutation SemCreateDimension($input: SemCreateDimensionInput!) {
|
|
282
|
+
semCreateDimension(input: $input) {
|
|
283
|
+
${DIMENSION_FIELDS}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
var CREATE_METRIC = `
|
|
288
|
+
mutation SemCreateMetric($input: SemCreateMetricInput!) {
|
|
289
|
+
semCreateMetric(input: $input) {
|
|
290
|
+
${METRIC_FIELDS}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
`;
|
|
294
|
+
var LIST_RELATIONSHIPS = `
|
|
295
|
+
query SemListRelationships($modelId: ID!) {
|
|
296
|
+
semListRelationships(modelId: $modelId) {
|
|
297
|
+
${RELATIONSHIP_FIELDS}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
`;
|
|
301
|
+
var CREATE_RELATIONSHIP = `
|
|
302
|
+
mutation SemCreateRelationship($input: SemCreateRelationshipInput!) {
|
|
303
|
+
semCreateRelationship(input: $input) {
|
|
304
|
+
${RELATIONSHIP_FIELDS}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
var GET_QUERY = `
|
|
309
|
+
query SemGetQuery($id: ID!) {
|
|
310
|
+
semQuery(id: $id) {
|
|
311
|
+
${QUERY_FIELDS}
|
|
312
|
+
runs { ${QUERY_RUN_FIELDS} }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
var LIST_QUERIES = `
|
|
317
|
+
query SemListQueries(
|
|
318
|
+
$modelId: ID
|
|
319
|
+
$status: SemQueryStatus
|
|
320
|
+
$pagination: PaginationInput
|
|
321
|
+
) {
|
|
322
|
+
semListQueries(modelId: $modelId, status: $status, pagination: $pagination) {
|
|
323
|
+
items { ${QUERY_FIELDS} }
|
|
324
|
+
${PAGE_INFO}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
var CREATE_QUERY = `
|
|
329
|
+
mutation SemCreateQuery($input: SemCreateQueryInput!) {
|
|
330
|
+
semCreateQuery(input: $input) {
|
|
331
|
+
${QUERY_FIELDS}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
`;
|
|
335
|
+
var UPDATE_QUERY = `
|
|
336
|
+
mutation SemUpdateQuery($id: ID!, $input: SemUpdateQueryInput!) {
|
|
337
|
+
semUpdateQuery(id: $id, input: $input) {
|
|
338
|
+
${QUERY_FIELDS}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
var PUBLISH_QUERY = `
|
|
343
|
+
mutation SemPublishQuery($id: ID!) {
|
|
344
|
+
semPublishQuery(id: $id) {
|
|
345
|
+
${QUERY_FIELDS}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
`;
|
|
349
|
+
var ARCHIVE_QUERY = `
|
|
350
|
+
mutation SemArchiveQuery($id: ID!) {
|
|
351
|
+
semArchiveQuery(id: $id)
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
var RUN_QUERY = `
|
|
355
|
+
mutation SemRunQuery($id: ID!) {
|
|
356
|
+
semRunQuery(id: $id) {
|
|
357
|
+
${QUERY_RUN_FIELDS}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
`;
|
|
361
|
+
var LIST_QUERY_RUNS = `
|
|
362
|
+
query SemListQueryRuns(
|
|
363
|
+
$queryId: ID!
|
|
364
|
+
$status: SemQueryRunStatus
|
|
365
|
+
$pagination: PaginationInput
|
|
366
|
+
) {
|
|
367
|
+
semListQueryRuns(queryId: $queryId, status: $status, pagination: $pagination) {
|
|
368
|
+
items { ${QUERY_RUN_FIELDS} }
|
|
369
|
+
${PAGE_INFO}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
`;
|
|
373
|
+
var LIST_ACCESS_POLICIES = `
|
|
374
|
+
query SemListAccessPolicies($modelId: ID, $entityId: ID) {
|
|
375
|
+
semListAccessPolicies(modelId: $modelId, entityId: $entityId) {
|
|
376
|
+
${ACCESS_POLICY_FIELDS}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
var CREATE_ACCESS_POLICY = `
|
|
381
|
+
mutation SemCreateAccessPolicy($input: SemCreateAccessPolicyInput!) {
|
|
382
|
+
semCreateAccessPolicy(input: $input) {
|
|
383
|
+
${ACCESS_POLICY_FIELDS}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
387
|
+
var SET_ACCESS_POLICY_ACTIVE = `
|
|
388
|
+
mutation SemSetAccessPolicyActive($id: ID!, $isActive: Boolean!) {
|
|
389
|
+
semSetAccessPolicyActive(id: $id, isActive: $isActive) {
|
|
390
|
+
${ACCESS_POLICY_FIELDS}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
`;
|
|
394
|
+
var HEALTH = `
|
|
395
|
+
query SemHealth {
|
|
396
|
+
semHealth
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
var ConnectorKindEnum = zod.z.enum([
|
|
400
|
+
"BIGQUERY",
|
|
401
|
+
"CLICKHOUSE",
|
|
402
|
+
"DATABRICKS",
|
|
403
|
+
"GRAPHQL_API",
|
|
404
|
+
"MONGODB",
|
|
405
|
+
"MYSQL",
|
|
406
|
+
"OTHER",
|
|
407
|
+
"POSTGRES",
|
|
408
|
+
"REDSHIFT",
|
|
409
|
+
"REST_API",
|
|
410
|
+
"S3_PARQUET",
|
|
411
|
+
"SNOWFLAKE"
|
|
412
|
+
]).describe("Backing technology of a connector");
|
|
413
|
+
var DataSourceStatusEnum = zod.z.enum(["CONNECTED", "DEGRADED", "DISABLED", "DISCONNECTED", "DRAFT"]).describe("Connectivity lifecycle status of a data source");
|
|
414
|
+
var ConnectionHealthEnum = zod.z.enum(["DEGRADED", "HEALTHY", "UNKNOWN", "UNREACHABLE"]).describe("Health classification reported by a connection probe");
|
|
415
|
+
var ModelStatusEnum = zod.z.enum(["ARCHIVED", "DEPRECATED", "DRAFT", "PUBLISHED"]).describe("Lifecycle status of a semantic model");
|
|
416
|
+
var EntityKindEnum = zod.z.enum(["DERIVED", "EXTERNAL", "TABLE", "VIEW"]).describe("Physical/logical kind of a semantic entity");
|
|
417
|
+
var DataTypeEnum = zod.z.enum(["BOOLEAN", "DATE", "DECIMAL", "GEO", "INTEGER", "JSON", "STRING", "TIMESTAMP"]).describe("Data type of a dimension");
|
|
418
|
+
var AggregationEnum = zod.z.enum(["AVG", "COUNT", "COUNT_DISTINCT", "CUSTOM", "MAX", "MEDIAN", "MIN", "SUM"]).describe("Aggregation function of a metric");
|
|
419
|
+
var RelationshipKindEnum = zod.z.enum(["MANY_TO_MANY", "MANY_TO_ONE", "ONE_TO_MANY", "ONE_TO_ONE"]).describe("Cardinality of a relationship between entities");
|
|
420
|
+
var QueryStatusEnum = zod.z.enum(["ARCHIVED", "DRAFT", "PUBLISHED"]).describe("Lifecycle status of a saved federated query");
|
|
421
|
+
var QueryRunStatusEnum = zod.z.enum(["CANCELLED", "FAILED", "PLANNING", "QUEUED", "RUNNING", "SUCCEEDED"]).describe("Execution status of a query run");
|
|
422
|
+
var PolicyEffectEnum = zod.z.enum(["ALLOW", "DENY"]).describe("Whether an access policy ALLOWs or DENYs");
|
|
423
|
+
var PolicyScopeEnum = zod.z.enum(["COLUMN", "ENTITY", "ROW"]).describe("Governance scope an access policy applies at");
|
|
424
|
+
var JsonObject = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
425
|
+
|
|
426
|
+
// src/modules/semanticfed/tools/connectors.ts
|
|
427
|
+
function registerConnectorTools(server, ctx, track) {
|
|
428
|
+
server.registerTool(
|
|
429
|
+
track(mcpToolName(ctx.productName, "connectors_get")),
|
|
430
|
+
{
|
|
431
|
+
title: "Get Connector",
|
|
432
|
+
description: "Fetch a single SemanticFed connector by ID, including its data sources.",
|
|
433
|
+
inputSchema: { id: zod.z.string().describe("Connector ID") }
|
|
434
|
+
},
|
|
435
|
+
async ({ id }) => {
|
|
436
|
+
try {
|
|
437
|
+
const data = await ctx.gql.workspace.request(GET_CONNECTOR, { id });
|
|
438
|
+
if (!data.semConnector) return errorResult(`Connector ${id} not found`);
|
|
439
|
+
return jsonResult(data.semConnector);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
ctx.logger.error("semanticfed connectors_get failed", { error: String(error) });
|
|
442
|
+
return errorResult(
|
|
443
|
+
`Failed to get connector: ${error instanceof Error ? error.message : String(error)}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
server.registerTool(
|
|
449
|
+
track(mcpToolName(ctx.productName, "connectors_list")),
|
|
450
|
+
{
|
|
451
|
+
title: "List Connectors",
|
|
452
|
+
description: "List connectors in the bound workspace, optionally filtered by kind, with limit/offset pagination.",
|
|
453
|
+
inputSchema: {
|
|
454
|
+
kind: ConnectorKindEnum.optional().describe("Filter by backing technology"),
|
|
455
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
456
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
async ({ kind, limit, offset }) => {
|
|
460
|
+
try {
|
|
461
|
+
const data = await ctx.gql.workspace.request(LIST_CONNECTORS, {
|
|
462
|
+
kind,
|
|
463
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0 }
|
|
464
|
+
});
|
|
465
|
+
return jsonResult(data.semListConnectors);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
ctx.logger.error("semanticfed connectors_list failed", { error: String(error) });
|
|
468
|
+
return errorResult(
|
|
469
|
+
`Failed to list connectors: ${error instanceof Error ? error.message : String(error)}`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
server.registerTool(
|
|
475
|
+
track(mcpToolName(ctx.productName, "connectors_create")),
|
|
476
|
+
{
|
|
477
|
+
title: "Create Connector",
|
|
478
|
+
description: "Register a new connector. `name` is required; `kind` defaults to POSTGRES. `capabilities` is optional JSON describing supported pushdown/features.",
|
|
479
|
+
inputSchema: {
|
|
480
|
+
name: zod.z.string().min(1).describe("Connector display name"),
|
|
481
|
+
kind: ConnectorKindEnum.optional().describe("Backing technology (default POSTGRES)"),
|
|
482
|
+
description: zod.z.string().optional().describe("Description"),
|
|
483
|
+
capabilities: JsonObject.optional().describe("Capabilities JSON (pushdown/features)")
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
async ({ name, kind, description, capabilities }) => {
|
|
487
|
+
try {
|
|
488
|
+
const input = { name };
|
|
489
|
+
if (kind !== void 0) input.kind = kind;
|
|
490
|
+
if (description !== void 0) input.description = description;
|
|
491
|
+
if (capabilities !== void 0) input.capabilities = capabilities;
|
|
492
|
+
const data = await ctx.gql.workspace.request(CREATE_CONNECTOR, { input });
|
|
493
|
+
return jsonResult(data.semCreateConnector);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
ctx.logger.error("semanticfed connectors_create failed", { error: String(error) });
|
|
496
|
+
return errorResult(
|
|
497
|
+
`Failed to create connector: ${error instanceof Error ? error.message : String(error)}`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
server.registerTool(
|
|
503
|
+
track(mcpToolName(ctx.productName, "connectors_update")),
|
|
504
|
+
{
|
|
505
|
+
title: "Update Connector",
|
|
506
|
+
description: "Update a connector. At least one mutable field must be provided. Use `isActive` to enable/disable it.",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
id: zod.z.string().describe("Connector ID"),
|
|
509
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
510
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
511
|
+
kind: ConnectorKindEnum.optional().describe("Updated backing technology"),
|
|
512
|
+
isActive: zod.z.boolean().optional().describe("Updated active flag"),
|
|
513
|
+
capabilities: JsonObject.optional().describe("Updated capabilities JSON")
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
async ({ id, name, description, kind, isActive, capabilities }) => {
|
|
517
|
+
try {
|
|
518
|
+
const input = {};
|
|
519
|
+
if (name !== void 0) input.name = name;
|
|
520
|
+
if (description !== void 0) input.description = description;
|
|
521
|
+
if (kind !== void 0) input.kind = kind;
|
|
522
|
+
if (isActive !== void 0) input.isActive = isActive;
|
|
523
|
+
if (capabilities !== void 0) input.capabilities = capabilities;
|
|
524
|
+
if (Object.keys(input).length === 0) {
|
|
525
|
+
return errorResult(
|
|
526
|
+
"No updates specified \u2014 provide at least one of name/description/kind/isActive/capabilities"
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const data = await ctx.gql.workspace.request(UPDATE_CONNECTOR, { id, input });
|
|
530
|
+
return jsonResult(data.semUpdateConnector);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
ctx.logger.error("semanticfed connectors_update failed", { error: String(error) });
|
|
533
|
+
return errorResult(
|
|
534
|
+
`Failed to update connector: ${error instanceof Error ? error.message : String(error)}`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
server.registerTool(
|
|
540
|
+
track(mcpToolName(ctx.productName, "connectors_archive")),
|
|
541
|
+
{
|
|
542
|
+
title: "Archive Connector",
|
|
543
|
+
description: "Archive (soft-delete) a connector by ID. Returns true on success.",
|
|
544
|
+
inputSchema: { id: zod.z.string().describe("Connector ID") }
|
|
545
|
+
},
|
|
546
|
+
async ({ id }) => {
|
|
547
|
+
try {
|
|
548
|
+
const data = await ctx.gql.workspace.request(
|
|
549
|
+
ARCHIVE_CONNECTOR,
|
|
550
|
+
{ id }
|
|
551
|
+
);
|
|
552
|
+
return jsonResult({ archived: data.semArchiveConnector });
|
|
553
|
+
} catch (error) {
|
|
554
|
+
ctx.logger.error("semanticfed connectors_archive failed", { error: String(error) });
|
|
555
|
+
return errorResult(
|
|
556
|
+
`Failed to archive connector: ${error instanceof Error ? error.message : String(error)}`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function registerDataSourceTools(server, ctx, track) {
|
|
563
|
+
server.registerTool(
|
|
564
|
+
track(mcpToolName(ctx.productName, "data_sources_get")),
|
|
565
|
+
{
|
|
566
|
+
title: "Get Data Source",
|
|
567
|
+
description: "Fetch a single SemanticFed data source by ID, including its connector and connection probes.",
|
|
568
|
+
inputSchema: { id: zod.z.string().describe("Data source ID") }
|
|
569
|
+
},
|
|
570
|
+
async ({ id }) => {
|
|
571
|
+
try {
|
|
572
|
+
const data = await ctx.gql.workspace.request(GET_DATA_SOURCE, { id });
|
|
573
|
+
if (!data.semDataSource) return errorResult(`Data source ${id} not found`);
|
|
574
|
+
return jsonResult(data.semDataSource);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
ctx.logger.error("semanticfed data_sources_get failed", { error: String(error) });
|
|
577
|
+
return errorResult(
|
|
578
|
+
`Failed to get data source: ${error instanceof Error ? error.message : String(error)}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
server.registerTool(
|
|
584
|
+
track(mcpToolName(ctx.productName, "data_sources_list")),
|
|
585
|
+
{
|
|
586
|
+
title: "List Data Sources",
|
|
587
|
+
description: "List data sources, optionally filtered by connector and/or status, with limit/offset pagination.",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
connectorId: zod.z.string().optional().describe("Filter by connector ID"),
|
|
590
|
+
status: DataSourceStatusEnum.optional().describe("Filter by connectivity status"),
|
|
591
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
592
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
async ({ connectorId, status, limit, offset }) => {
|
|
596
|
+
try {
|
|
597
|
+
const data = await ctx.gql.workspace.request(LIST_DATA_SOURCES, {
|
|
598
|
+
connectorId,
|
|
599
|
+
status,
|
|
600
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0 }
|
|
601
|
+
});
|
|
602
|
+
return jsonResult(data.semListDataSources);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
ctx.logger.error("semanticfed data_sources_list failed", { error: String(error) });
|
|
605
|
+
return errorResult(
|
|
606
|
+
`Failed to list data sources: ${error instanceof Error ? error.message : String(error)}`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
server.registerTool(
|
|
612
|
+
track(mcpToolName(ctx.productName, "data_sources_create")),
|
|
613
|
+
{
|
|
614
|
+
title: "Create Data Source",
|
|
615
|
+
description: "Register a new data source against a connector. `connectorId` and `name` are required; `endpoint` is connection JSON and `pushdownEnabled` defaults to true.",
|
|
616
|
+
inputSchema: {
|
|
617
|
+
connectorId: zod.z.string().min(1).describe("Parent connector ID"),
|
|
618
|
+
name: zod.z.string().min(1).describe("Data source display name"),
|
|
619
|
+
description: zod.z.string().optional().describe("Description"),
|
|
620
|
+
endpoint: JsonObject.optional().describe("Endpoint/connection JSON"),
|
|
621
|
+
secretRef: zod.z.string().optional().describe("Key Vault secret reference for credentials"),
|
|
622
|
+
pushdownEnabled: zod.z.boolean().optional().describe("Whether query pushdown is enabled (default true)"),
|
|
623
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Tag labels")
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
async ({ connectorId, name, description, endpoint, secretRef, pushdownEnabled, tags }) => {
|
|
627
|
+
try {
|
|
628
|
+
const input = { connectorId, name };
|
|
629
|
+
if (description !== void 0) input.description = description;
|
|
630
|
+
if (endpoint !== void 0) input.endpoint = endpoint;
|
|
631
|
+
if (secretRef !== void 0) input.secretRef = secretRef;
|
|
632
|
+
if (pushdownEnabled !== void 0) input.pushdownEnabled = pushdownEnabled;
|
|
633
|
+
if (tags !== void 0) input.tags = tags;
|
|
634
|
+
const data = await ctx.gql.workspace.request(CREATE_DATA_SOURCE, { input });
|
|
635
|
+
return jsonResult(data.semCreateDataSource);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
ctx.logger.error("semanticfed data_sources_create failed", { error: String(error) });
|
|
638
|
+
return errorResult(
|
|
639
|
+
`Failed to create data source: ${error instanceof Error ? error.message : String(error)}`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
server.registerTool(
|
|
645
|
+
track(mcpToolName(ctx.productName, "data_sources_update")),
|
|
646
|
+
{
|
|
647
|
+
title: "Update Data Source",
|
|
648
|
+
description: "Update a data source. At least one mutable field must be provided. Use `data_sources_transition` to change connectivity status.",
|
|
649
|
+
inputSchema: {
|
|
650
|
+
id: zod.z.string().describe("Data source ID"),
|
|
651
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
652
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
653
|
+
endpoint: JsonObject.optional().describe("Updated endpoint/connection JSON"),
|
|
654
|
+
secretRef: zod.z.string().optional().describe("Updated secret reference"),
|
|
655
|
+
pushdownEnabled: zod.z.boolean().optional().describe("Updated pushdown flag"),
|
|
656
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels")
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
async ({ id, name, description, endpoint, secretRef, pushdownEnabled, tags }) => {
|
|
660
|
+
try {
|
|
661
|
+
const input = {};
|
|
662
|
+
if (name !== void 0) input.name = name;
|
|
663
|
+
if (description !== void 0) input.description = description;
|
|
664
|
+
if (endpoint !== void 0) input.endpoint = endpoint;
|
|
665
|
+
if (secretRef !== void 0) input.secretRef = secretRef;
|
|
666
|
+
if (pushdownEnabled !== void 0) input.pushdownEnabled = pushdownEnabled;
|
|
667
|
+
if (tags !== void 0) input.tags = tags;
|
|
668
|
+
if (Object.keys(input).length === 0) {
|
|
669
|
+
return errorResult(
|
|
670
|
+
"No updates specified \u2014 provide at least one of name/description/endpoint/secretRef/pushdownEnabled/tags"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const data = await ctx.gql.workspace.request(UPDATE_DATA_SOURCE, { id, input });
|
|
674
|
+
return jsonResult(data.semUpdateDataSource);
|
|
675
|
+
} catch (error) {
|
|
676
|
+
ctx.logger.error("semanticfed data_sources_update failed", { error: String(error) });
|
|
677
|
+
return errorResult(
|
|
678
|
+
`Failed to update data source: ${error instanceof Error ? error.message : String(error)}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
);
|
|
683
|
+
server.registerTool(
|
|
684
|
+
track(mcpToolName(ctx.productName, "data_sources_transition")),
|
|
685
|
+
{
|
|
686
|
+
title: "Transition Data Source",
|
|
687
|
+
description: "Move a data source through its connectivity lifecycle via a validated transition (DRAFT \u2192 CONNECTED \u2192 DEGRADED/DISCONNECTED/DISABLED).",
|
|
688
|
+
inputSchema: {
|
|
689
|
+
id: zod.z.string().describe("Data source ID"),
|
|
690
|
+
status: DataSourceStatusEnum.describe("Target connectivity status")
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
async ({ id, status }) => {
|
|
694
|
+
try {
|
|
695
|
+
const data = await ctx.gql.workspace.request(TRANSITION_DATA_SOURCE, { id, status });
|
|
696
|
+
return jsonResult(data.semTransitionDataSource);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
ctx.logger.error("semanticfed data_sources_transition failed", { error: String(error) });
|
|
699
|
+
return errorResult(
|
|
700
|
+
`Failed to transition data source: ${error instanceof Error ? error.message : String(error)}`
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
server.registerTool(
|
|
706
|
+
track(mcpToolName(ctx.productName, "data_sources_archive")),
|
|
707
|
+
{
|
|
708
|
+
title: "Archive Data Source",
|
|
709
|
+
description: "Archive (soft-delete) a data source by ID. Returns true on success.",
|
|
710
|
+
inputSchema: { id: zod.z.string().describe("Data source ID") }
|
|
711
|
+
},
|
|
712
|
+
async ({ id }) => {
|
|
713
|
+
try {
|
|
714
|
+
const data = await ctx.gql.workspace.request(
|
|
715
|
+
ARCHIVE_DATA_SOURCE,
|
|
716
|
+
{ id }
|
|
717
|
+
);
|
|
718
|
+
return jsonResult({ archived: data.semArchiveDataSource });
|
|
719
|
+
} catch (error) {
|
|
720
|
+
ctx.logger.error("semanticfed data_sources_archive failed", { error: String(error) });
|
|
721
|
+
return errorResult(
|
|
722
|
+
`Failed to archive data source: ${error instanceof Error ? error.message : String(error)}`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
function registerConnectionTools(server, ctx, track) {
|
|
729
|
+
server.registerTool(
|
|
730
|
+
track(mcpToolName(ctx.productName, "connections_list")),
|
|
731
|
+
{
|
|
732
|
+
title: "List Connection Probes",
|
|
733
|
+
description: "List the recorded connection health probes for a data source.",
|
|
734
|
+
inputSchema: { dataSourceId: zod.z.string().describe("Data source ID") }
|
|
735
|
+
},
|
|
736
|
+
async ({ dataSourceId }) => {
|
|
737
|
+
try {
|
|
738
|
+
const data = await ctx.gql.workspace.request(LIST_CONNECTIONS, { dataSourceId });
|
|
739
|
+
return jsonResult(data.semListConnections);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
ctx.logger.error("semanticfed connections_list failed", { error: String(error) });
|
|
742
|
+
return errorResult(
|
|
743
|
+
`Failed to list connections: ${error instanceof Error ? error.message : String(error)}`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
);
|
|
748
|
+
server.registerTool(
|
|
749
|
+
track(mcpToolName(ctx.productName, "connections_record")),
|
|
750
|
+
{
|
|
751
|
+
title: "Record Connection Probe",
|
|
752
|
+
description: "Record a connection health probe for a data source and reconcile its status. `dataSourceId` and `health` are required.",
|
|
753
|
+
inputSchema: {
|
|
754
|
+
dataSourceId: zod.z.string().min(1).describe("Data source ID"),
|
|
755
|
+
health: ConnectionHealthEnum.describe("Observed health"),
|
|
756
|
+
latencyMs: zod.z.number().int().optional().describe("Probe round-trip latency in ms"),
|
|
757
|
+
message: zod.z.string().optional().describe("Probe message / diagnostic detail")
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
async ({ dataSourceId, health, latencyMs, message }) => {
|
|
761
|
+
try {
|
|
762
|
+
const input = { dataSourceId, health };
|
|
763
|
+
if (latencyMs !== void 0) input.latencyMs = latencyMs;
|
|
764
|
+
if (message !== void 0) input.message = message;
|
|
765
|
+
const data = await ctx.gql.workspace.request(RECORD_CONNECTION, { input });
|
|
766
|
+
return jsonResult(data.semRecordConnection);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
ctx.logger.error("semanticfed connections_record failed", { error: String(error) });
|
|
769
|
+
return errorResult(
|
|
770
|
+
`Failed to record connection: ${error instanceof Error ? error.message : String(error)}`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
function registerModelTools(server, ctx, track) {
|
|
777
|
+
server.registerTool(
|
|
778
|
+
track(mcpToolName(ctx.productName, "models_get")),
|
|
779
|
+
{
|
|
780
|
+
title: "Get Semantic Model",
|
|
781
|
+
description: "Fetch a single semantic model by ID, including its entities and relationships.",
|
|
782
|
+
inputSchema: { id: zod.z.string().describe("Semantic model ID") }
|
|
783
|
+
},
|
|
784
|
+
async ({ id }) => {
|
|
785
|
+
try {
|
|
786
|
+
const data = await ctx.gql.workspace.request(GET_MODEL, { id });
|
|
787
|
+
if (!data.semModel) return errorResult(`Semantic model ${id} not found`);
|
|
788
|
+
return jsonResult(data.semModel);
|
|
789
|
+
} catch (error) {
|
|
790
|
+
ctx.logger.error("semanticfed models_get failed", { error: String(error) });
|
|
791
|
+
return errorResult(
|
|
792
|
+
`Failed to get semantic model: ${error instanceof Error ? error.message : String(error)}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
server.registerTool(
|
|
798
|
+
track(mcpToolName(ctx.productName, "models_list")),
|
|
799
|
+
{
|
|
800
|
+
title: "List Semantic Models",
|
|
801
|
+
description: "List semantic models in the bound workspace, optionally filtered by status, with limit/offset pagination.",
|
|
802
|
+
inputSchema: {
|
|
803
|
+
status: ModelStatusEnum.optional().describe("Filter by model status"),
|
|
804
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
805
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
async ({ status, limit, offset }) => {
|
|
809
|
+
try {
|
|
810
|
+
const data = await ctx.gql.workspace.request(LIST_MODELS, {
|
|
811
|
+
status,
|
|
812
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0 }
|
|
813
|
+
});
|
|
814
|
+
return jsonResult(data.semListModels);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
ctx.logger.error("semanticfed models_list failed", { error: String(error) });
|
|
817
|
+
return errorResult(
|
|
818
|
+
`Failed to list semantic models: ${error instanceof Error ? error.message : String(error)}`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
server.registerTool(
|
|
824
|
+
track(mcpToolName(ctx.productName, "models_create")),
|
|
825
|
+
{
|
|
826
|
+
title: "Create Semantic Model",
|
|
827
|
+
description: "Create a new draft semantic model. `name` is required; entities/relationships are added separately.",
|
|
828
|
+
inputSchema: {
|
|
829
|
+
name: zod.z.string().min(1).describe("Model display name"),
|
|
830
|
+
description: zod.z.string().optional().describe("Description"),
|
|
831
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Tag labels")
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
async ({ name, description, tags }) => {
|
|
835
|
+
try {
|
|
836
|
+
const input = { name };
|
|
837
|
+
if (description !== void 0) input.description = description;
|
|
838
|
+
if (tags !== void 0) input.tags = tags;
|
|
839
|
+
const data = await ctx.gql.workspace.request(CREATE_MODEL, { input });
|
|
840
|
+
return jsonResult(data.semCreateModel);
|
|
841
|
+
} catch (error) {
|
|
842
|
+
ctx.logger.error("semanticfed models_create failed", { error: String(error) });
|
|
843
|
+
return errorResult(
|
|
844
|
+
`Failed to create semantic model: ${error instanceof Error ? error.message : String(error)}`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
server.registerTool(
|
|
850
|
+
track(mcpToolName(ctx.productName, "models_update")),
|
|
851
|
+
{
|
|
852
|
+
title: "Update Semantic Model",
|
|
853
|
+
description: "Update a semantic model. At least one mutable field must be provided. Use `models_publish` to publish a draft.",
|
|
854
|
+
inputSchema: {
|
|
855
|
+
id: zod.z.string().describe("Semantic model ID"),
|
|
856
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
857
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
858
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels")
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
async ({ id, name, description, tags }) => {
|
|
862
|
+
try {
|
|
863
|
+
const input = {};
|
|
864
|
+
if (name !== void 0) input.name = name;
|
|
865
|
+
if (description !== void 0) input.description = description;
|
|
866
|
+
if (tags !== void 0) input.tags = tags;
|
|
867
|
+
if (Object.keys(input).length === 0) {
|
|
868
|
+
return errorResult(
|
|
869
|
+
"No updates specified \u2014 provide at least one of name/description/tags"
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
const data = await ctx.gql.workspace.request(UPDATE_MODEL, { id, input });
|
|
873
|
+
return jsonResult(data.semUpdateModel);
|
|
874
|
+
} catch (error) {
|
|
875
|
+
ctx.logger.error("semanticfed models_update failed", { error: String(error) });
|
|
876
|
+
return errorResult(
|
|
877
|
+
`Failed to update semantic model: ${error instanceof Error ? error.message : String(error)}`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
);
|
|
882
|
+
server.registerTool(
|
|
883
|
+
track(mcpToolName(ctx.productName, "models_publish")),
|
|
884
|
+
{
|
|
885
|
+
title: "Publish Semantic Model",
|
|
886
|
+
description: "Publish a draft model \u2014 bumps its version and flips status to PUBLISHED. Returns the published model.",
|
|
887
|
+
inputSchema: { id: zod.z.string().describe("Semantic model ID") }
|
|
888
|
+
},
|
|
889
|
+
async ({ id }) => {
|
|
890
|
+
try {
|
|
891
|
+
const data = await ctx.gql.workspace.request(PUBLISH_MODEL, { id });
|
|
892
|
+
return jsonResult(data.semPublishModel);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
ctx.logger.error("semanticfed models_publish failed", { error: String(error) });
|
|
895
|
+
return errorResult(
|
|
896
|
+
`Failed to publish semantic model: ${error instanceof Error ? error.message : String(error)}`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
server.registerTool(
|
|
902
|
+
track(mcpToolName(ctx.productName, "models_archive")),
|
|
903
|
+
{
|
|
904
|
+
title: "Archive Semantic Model",
|
|
905
|
+
description: "Archive (soft-delete) a semantic model by ID. Returns true on success.",
|
|
906
|
+
inputSchema: { id: zod.z.string().describe("Semantic model ID") }
|
|
907
|
+
},
|
|
908
|
+
async ({ id }) => {
|
|
909
|
+
try {
|
|
910
|
+
const data = await ctx.gql.workspace.request(
|
|
911
|
+
ARCHIVE_MODEL,
|
|
912
|
+
{ id }
|
|
913
|
+
);
|
|
914
|
+
return jsonResult({ archived: data.semArchiveModel });
|
|
915
|
+
} catch (error) {
|
|
916
|
+
ctx.logger.error("semanticfed models_archive failed", { error: String(error) });
|
|
917
|
+
return errorResult(
|
|
918
|
+
`Failed to archive semantic model: ${error instanceof Error ? error.message : String(error)}`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
function registerEntityTools(server, ctx, track) {
|
|
925
|
+
server.registerTool(
|
|
926
|
+
track(mcpToolName(ctx.productName, "entities_get")),
|
|
927
|
+
{
|
|
928
|
+
title: "Get Entity",
|
|
929
|
+
description: "Fetch a single entity by ID, including its dimensions and metrics.",
|
|
930
|
+
inputSchema: { id: zod.z.string().describe("Entity ID") }
|
|
931
|
+
},
|
|
932
|
+
async ({ id }) => {
|
|
933
|
+
try {
|
|
934
|
+
const data = await ctx.gql.workspace.request(GET_ENTITY, { id });
|
|
935
|
+
if (!data.semEntity) return errorResult(`Entity ${id} not found`);
|
|
936
|
+
return jsonResult(data.semEntity);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
ctx.logger.error("semanticfed entities_get failed", { error: String(error) });
|
|
939
|
+
return errorResult(
|
|
940
|
+
`Failed to get entity: ${error instanceof Error ? error.message : String(error)}`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
server.registerTool(
|
|
946
|
+
track(mcpToolName(ctx.productName, "entities_list")),
|
|
947
|
+
{
|
|
948
|
+
title: "List Entities",
|
|
949
|
+
description: "List the entities that belong to a semantic model.",
|
|
950
|
+
inputSchema: { modelId: zod.z.string().describe("Owning semantic model ID") }
|
|
951
|
+
},
|
|
952
|
+
async ({ modelId }) => {
|
|
953
|
+
try {
|
|
954
|
+
const data = await ctx.gql.workspace.request(LIST_ENTITIES, { modelId });
|
|
955
|
+
return jsonResult(data.semListEntities);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
ctx.logger.error("semanticfed entities_list failed", { error: String(error) });
|
|
958
|
+
return errorResult(
|
|
959
|
+
`Failed to list entities: ${error instanceof Error ? error.message : String(error)}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
server.registerTool(
|
|
965
|
+
track(mcpToolName(ctx.productName, "entities_create")),
|
|
966
|
+
{
|
|
967
|
+
title: "Create Entity",
|
|
968
|
+
description: "Create an entity in a model. `modelId` and `name` are required; `kind` defaults to TABLE. Bind it to a `dataSourceId` or define it with `sqlDefinition` for derived entities.",
|
|
969
|
+
inputSchema: {
|
|
970
|
+
modelId: zod.z.string().min(1).describe("Owning semantic model ID"),
|
|
971
|
+
name: zod.z.string().min(1).describe("Entity display name"),
|
|
972
|
+
kind: EntityKindEnum.optional().describe("Entity kind (default TABLE)"),
|
|
973
|
+
dataSourceId: zod.z.string().optional().describe("Backing data source ID"),
|
|
974
|
+
description: zod.z.string().optional().describe("Description"),
|
|
975
|
+
physicalName: zod.z.string().optional().describe("Physical table/view name"),
|
|
976
|
+
primaryKey: zod.z.string().optional().describe("Primary-key column"),
|
|
977
|
+
sqlDefinition: zod.z.string().optional().describe("SQL definition (for DERIVED/VIEW entities)")
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
async ({
|
|
981
|
+
modelId,
|
|
982
|
+
name,
|
|
983
|
+
kind,
|
|
984
|
+
dataSourceId,
|
|
985
|
+
description,
|
|
986
|
+
physicalName,
|
|
987
|
+
primaryKey,
|
|
988
|
+
sqlDefinition
|
|
989
|
+
}) => {
|
|
990
|
+
try {
|
|
991
|
+
const input = { modelId, name };
|
|
992
|
+
if (kind !== void 0) input.kind = kind;
|
|
993
|
+
if (dataSourceId !== void 0) input.dataSourceId = dataSourceId;
|
|
994
|
+
if (description !== void 0) input.description = description;
|
|
995
|
+
if (physicalName !== void 0) input.physicalName = physicalName;
|
|
996
|
+
if (primaryKey !== void 0) input.primaryKey = primaryKey;
|
|
997
|
+
if (sqlDefinition !== void 0) input.sqlDefinition = sqlDefinition;
|
|
998
|
+
const data = await ctx.gql.workspace.request(CREATE_ENTITY, { input });
|
|
999
|
+
return jsonResult(data.semCreateEntity);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
ctx.logger.error("semanticfed entities_create failed", { error: String(error) });
|
|
1002
|
+
return errorResult(
|
|
1003
|
+
`Failed to create entity: ${error instanceof Error ? error.message : String(error)}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
server.registerTool(
|
|
1009
|
+
track(mcpToolName(ctx.productName, "entities_update")),
|
|
1010
|
+
{
|
|
1011
|
+
title: "Update Entity",
|
|
1012
|
+
description: "Update an entity. At least one mutable field must be provided.",
|
|
1013
|
+
inputSchema: {
|
|
1014
|
+
id: zod.z.string().describe("Entity ID"),
|
|
1015
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
1016
|
+
kind: EntityKindEnum.optional().describe("Updated entity kind"),
|
|
1017
|
+
dataSourceId: zod.z.string().optional().describe("Updated backing data source ID"),
|
|
1018
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
1019
|
+
physicalName: zod.z.string().optional().describe("Updated physical table/view name"),
|
|
1020
|
+
primaryKey: zod.z.string().optional().describe("Updated primary-key column"),
|
|
1021
|
+
sqlDefinition: zod.z.string().optional().describe("Updated SQL definition")
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
async ({
|
|
1025
|
+
id,
|
|
1026
|
+
name,
|
|
1027
|
+
kind,
|
|
1028
|
+
dataSourceId,
|
|
1029
|
+
description,
|
|
1030
|
+
physicalName,
|
|
1031
|
+
primaryKey,
|
|
1032
|
+
sqlDefinition
|
|
1033
|
+
}) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const input = {};
|
|
1036
|
+
if (name !== void 0) input.name = name;
|
|
1037
|
+
if (kind !== void 0) input.kind = kind;
|
|
1038
|
+
if (dataSourceId !== void 0) input.dataSourceId = dataSourceId;
|
|
1039
|
+
if (description !== void 0) input.description = description;
|
|
1040
|
+
if (physicalName !== void 0) input.physicalName = physicalName;
|
|
1041
|
+
if (primaryKey !== void 0) input.primaryKey = primaryKey;
|
|
1042
|
+
if (sqlDefinition !== void 0) input.sqlDefinition = sqlDefinition;
|
|
1043
|
+
if (Object.keys(input).length === 0) {
|
|
1044
|
+
return errorResult(
|
|
1045
|
+
"No updates specified \u2014 provide at least one of name/kind/dataSourceId/description/physicalName/primaryKey/sqlDefinition"
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
const data = await ctx.gql.workspace.request(UPDATE_ENTITY, { id, input });
|
|
1049
|
+
return jsonResult(data.semUpdateEntity);
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
ctx.logger.error("semanticfed entities_update failed", { error: String(error) });
|
|
1052
|
+
return errorResult(
|
|
1053
|
+
`Failed to update entity: ${error instanceof Error ? error.message : String(error)}`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
server.registerTool(
|
|
1059
|
+
track(mcpToolName(ctx.productName, "entities_delete")),
|
|
1060
|
+
{
|
|
1061
|
+
title: "Delete Entity",
|
|
1062
|
+
description: "Delete an entity by ID (also removes its dimensions/metrics). Returns true on success.",
|
|
1063
|
+
inputSchema: { id: zod.z.string().describe("Entity ID") }
|
|
1064
|
+
},
|
|
1065
|
+
async ({ id }) => {
|
|
1066
|
+
try {
|
|
1067
|
+
const data = await ctx.gql.workspace.request(
|
|
1068
|
+
DELETE_ENTITY,
|
|
1069
|
+
{ id }
|
|
1070
|
+
);
|
|
1071
|
+
return jsonResult({ deleted: data.semDeleteEntity });
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
ctx.logger.error("semanticfed entities_delete failed", { error: String(error) });
|
|
1074
|
+
return errorResult(
|
|
1075
|
+
`Failed to delete entity: ${error instanceof Error ? error.message : String(error)}`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
function registerDimensionTools(server, ctx, track) {
|
|
1082
|
+
server.registerTool(
|
|
1083
|
+
track(mcpToolName(ctx.productName, "dimensions_create")),
|
|
1084
|
+
{
|
|
1085
|
+
title: "Create Dimension",
|
|
1086
|
+
description: "Add a dimension (descriptive attribute) to an entity. `entityId` and `name` are required; `dataType` defaults to STRING and `isPrimary` to false.",
|
|
1087
|
+
inputSchema: {
|
|
1088
|
+
entityId: zod.z.string().min(1).describe("Owning entity ID"),
|
|
1089
|
+
name: zod.z.string().min(1).describe("Dimension display name"),
|
|
1090
|
+
dataType: DataTypeEnum.optional().describe("Data type (default STRING)"),
|
|
1091
|
+
description: zod.z.string().optional().describe("Description"),
|
|
1092
|
+
expression: zod.z.string().optional().describe("SQL expression backing the dimension"),
|
|
1093
|
+
isPrimary: zod.z.boolean().optional().describe("Whether this is the primary dimension (default false)")
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
async ({ entityId, name, dataType, description, expression, isPrimary }) => {
|
|
1097
|
+
try {
|
|
1098
|
+
const input = { entityId, name };
|
|
1099
|
+
if (dataType !== void 0) input.dataType = dataType;
|
|
1100
|
+
if (description !== void 0) input.description = description;
|
|
1101
|
+
if (expression !== void 0) input.expression = expression;
|
|
1102
|
+
if (isPrimary !== void 0) input.isPrimary = isPrimary;
|
|
1103
|
+
const data = await ctx.gql.workspace.request(CREATE_DIMENSION, { input });
|
|
1104
|
+
return jsonResult(data.semCreateDimension);
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
ctx.logger.error("semanticfed dimensions_create failed", { error: String(error) });
|
|
1107
|
+
return errorResult(
|
|
1108
|
+
`Failed to create dimension: ${error instanceof Error ? error.message : String(error)}`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
function registerMetricTools(server, ctx, track) {
|
|
1115
|
+
server.registerTool(
|
|
1116
|
+
track(mcpToolName(ctx.productName, "metrics_create")),
|
|
1117
|
+
{
|
|
1118
|
+
title: "Create Metric",
|
|
1119
|
+
description: "Add a metric (aggregated measure) to an entity. `entityId` and `name` are required; `aggregation` defaults to SUM. Use `expression` for a custom SQL measure and `format` for display formatting.",
|
|
1120
|
+
inputSchema: {
|
|
1121
|
+
entityId: zod.z.string().min(1).describe("Owning entity ID"),
|
|
1122
|
+
name: zod.z.string().min(1).describe("Metric display name"),
|
|
1123
|
+
aggregation: AggregationEnum.optional().describe("Aggregation function (default SUM)"),
|
|
1124
|
+
description: zod.z.string().optional().describe("Description"),
|
|
1125
|
+
expression: zod.z.string().optional().describe("SQL expression backing the metric"),
|
|
1126
|
+
format: zod.z.string().optional().describe("Display format hint (e.g. currency, percent)")
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
async ({ entityId, name, aggregation, description, expression, format }) => {
|
|
1130
|
+
try {
|
|
1131
|
+
const input = { entityId, name };
|
|
1132
|
+
if (aggregation !== void 0) input.aggregation = aggregation;
|
|
1133
|
+
if (description !== void 0) input.description = description;
|
|
1134
|
+
if (expression !== void 0) input.expression = expression;
|
|
1135
|
+
if (format !== void 0) input.format = format;
|
|
1136
|
+
const data = await ctx.gql.workspace.request(CREATE_METRIC, { input });
|
|
1137
|
+
return jsonResult(data.semCreateMetric);
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
ctx.logger.error("semanticfed metrics_create failed", { error: String(error) });
|
|
1140
|
+
return errorResult(
|
|
1141
|
+
`Failed to create metric: ${error instanceof Error ? error.message : String(error)}`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
function registerRelationshipTools(server, ctx, track) {
|
|
1148
|
+
server.registerTool(
|
|
1149
|
+
track(mcpToolName(ctx.productName, "relationships_list")),
|
|
1150
|
+
{
|
|
1151
|
+
title: "List Relationships",
|
|
1152
|
+
description: "List the relationships (entity joins) that belong to a semantic model.",
|
|
1153
|
+
inputSchema: { modelId: zod.z.string().describe("Owning semantic model ID") }
|
|
1154
|
+
},
|
|
1155
|
+
async ({ modelId }) => {
|
|
1156
|
+
try {
|
|
1157
|
+
const data = await ctx.gql.workspace.request(LIST_RELATIONSHIPS, { modelId });
|
|
1158
|
+
return jsonResult(data.semListRelationships);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
ctx.logger.error("semanticfed relationships_list failed", { error: String(error) });
|
|
1161
|
+
return errorResult(
|
|
1162
|
+
`Failed to list relationships: ${error instanceof Error ? error.message : String(error)}`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
server.registerTool(
|
|
1168
|
+
track(mcpToolName(ctx.productName, "relationships_create")),
|
|
1169
|
+
{
|
|
1170
|
+
title: "Create Relationship",
|
|
1171
|
+
description: "Define a join between two entities in a model. `modelId`, `name`, `fromEntityId`, `fromField`, `toEntityId`, and `toField` are required; `kind` defaults to MANY_TO_ONE.",
|
|
1172
|
+
inputSchema: {
|
|
1173
|
+
modelId: zod.z.string().min(1).describe("Owning semantic model ID"),
|
|
1174
|
+
name: zod.z.string().min(1).describe("Relationship display name"),
|
|
1175
|
+
fromEntityId: zod.z.string().min(1).describe("Source entity ID"),
|
|
1176
|
+
fromField: zod.z.string().min(1).describe("Source join field"),
|
|
1177
|
+
toEntityId: zod.z.string().min(1).describe("Target entity ID"),
|
|
1178
|
+
toField: zod.z.string().min(1).describe("Target join field"),
|
|
1179
|
+
kind: RelationshipKindEnum.optional().describe("Cardinality (default MANY_TO_ONE)"),
|
|
1180
|
+
description: zod.z.string().optional().describe("Description")
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
async ({ modelId, name, fromEntityId, fromField, toEntityId, toField, kind, description }) => {
|
|
1184
|
+
try {
|
|
1185
|
+
const input = {
|
|
1186
|
+
modelId,
|
|
1187
|
+
name,
|
|
1188
|
+
fromEntityId,
|
|
1189
|
+
fromField,
|
|
1190
|
+
toEntityId,
|
|
1191
|
+
toField
|
|
1192
|
+
};
|
|
1193
|
+
if (kind !== void 0) input.kind = kind;
|
|
1194
|
+
if (description !== void 0) input.description = description;
|
|
1195
|
+
const data = await ctx.gql.workspace.request(CREATE_RELATIONSHIP, { input });
|
|
1196
|
+
return jsonResult(data.semCreateRelationship);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
ctx.logger.error("semanticfed relationships_create failed", { error: String(error) });
|
|
1199
|
+
return errorResult(
|
|
1200
|
+
`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
function registerQueryTools(server, ctx, track) {
|
|
1207
|
+
server.registerTool(
|
|
1208
|
+
track(mcpToolName(ctx.productName, "queries_get")),
|
|
1209
|
+
{
|
|
1210
|
+
title: "Get Query",
|
|
1211
|
+
description: "Fetch a single saved query by ID, including its run history.",
|
|
1212
|
+
inputSchema: { id: zod.z.string().describe("Query ID") }
|
|
1213
|
+
},
|
|
1214
|
+
async ({ id }) => {
|
|
1215
|
+
try {
|
|
1216
|
+
const data = await ctx.gql.workspace.request(GET_QUERY, { id });
|
|
1217
|
+
if (!data.semQuery) return errorResult(`Query ${id} not found`);
|
|
1218
|
+
return jsonResult(data.semQuery);
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
ctx.logger.error("semanticfed queries_get failed", { error: String(error) });
|
|
1221
|
+
return errorResult(
|
|
1222
|
+
`Failed to get query: ${error instanceof Error ? error.message : String(error)}`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
server.registerTool(
|
|
1228
|
+
track(mcpToolName(ctx.productName, "queries_list")),
|
|
1229
|
+
{
|
|
1230
|
+
title: "List Queries",
|
|
1231
|
+
description: "List saved queries, optionally filtered by model and/or status, with limit/offset pagination.",
|
|
1232
|
+
inputSchema: {
|
|
1233
|
+
modelId: zod.z.string().optional().describe("Filter by semantic model ID"),
|
|
1234
|
+
status: QueryStatusEnum.optional().describe("Filter by query status"),
|
|
1235
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1236
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
async ({ modelId, status, limit, offset }) => {
|
|
1240
|
+
try {
|
|
1241
|
+
const data = await ctx.gql.workspace.request(LIST_QUERIES, {
|
|
1242
|
+
modelId,
|
|
1243
|
+
status,
|
|
1244
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0 }
|
|
1245
|
+
});
|
|
1246
|
+
return jsonResult(data.semListQueries);
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
ctx.logger.error("semanticfed queries_list failed", { error: String(error) });
|
|
1249
|
+
return errorResult(
|
|
1250
|
+
`Failed to list queries: ${error instanceof Error ? error.message : String(error)}`
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
);
|
|
1255
|
+
server.registerTool(
|
|
1256
|
+
track(mcpToolName(ctx.productName, "queries_create")),
|
|
1257
|
+
{
|
|
1258
|
+
title: "Create Query",
|
|
1259
|
+
description: "Create a new draft saved query. `name` and `queryText` are required; `cacheable` defaults to true. Bind to a `modelId` to resolve entities/metrics by name.",
|
|
1260
|
+
inputSchema: {
|
|
1261
|
+
name: zod.z.string().min(1).describe("Query display name"),
|
|
1262
|
+
queryText: zod.z.string().min(1).describe("Federated query text"),
|
|
1263
|
+
modelId: zod.z.string().optional().describe("Semantic model ID the query resolves against"),
|
|
1264
|
+
description: zod.z.string().optional().describe("Description"),
|
|
1265
|
+
parameters: JsonObject.optional().describe("Parameter defaults/schema JSON"),
|
|
1266
|
+
cacheable: zod.z.boolean().optional().describe("Whether results are cacheable (default true)"),
|
|
1267
|
+
cacheTtlSeconds: zod.z.number().int().optional().describe("Cache TTL in seconds"),
|
|
1268
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Tag labels")
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
async ({
|
|
1272
|
+
name,
|
|
1273
|
+
queryText,
|
|
1274
|
+
modelId,
|
|
1275
|
+
description,
|
|
1276
|
+
parameters,
|
|
1277
|
+
cacheable,
|
|
1278
|
+
cacheTtlSeconds,
|
|
1279
|
+
tags
|
|
1280
|
+
}) => {
|
|
1281
|
+
try {
|
|
1282
|
+
const input = { name, queryText };
|
|
1283
|
+
if (modelId !== void 0) input.modelId = modelId;
|
|
1284
|
+
if (description !== void 0) input.description = description;
|
|
1285
|
+
if (parameters !== void 0) input.parameters = parameters;
|
|
1286
|
+
if (cacheable !== void 0) input.cacheable = cacheable;
|
|
1287
|
+
if (cacheTtlSeconds !== void 0) input.cacheTtlSeconds = cacheTtlSeconds;
|
|
1288
|
+
if (tags !== void 0) input.tags = tags;
|
|
1289
|
+
const data = await ctx.gql.workspace.request(CREATE_QUERY, { input });
|
|
1290
|
+
return jsonResult(data.semCreateQuery);
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
ctx.logger.error("semanticfed queries_create failed", { error: String(error) });
|
|
1293
|
+
return errorResult(
|
|
1294
|
+
`Failed to create query: ${error instanceof Error ? error.message : String(error)}`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
);
|
|
1299
|
+
server.registerTool(
|
|
1300
|
+
track(mcpToolName(ctx.productName, "queries_update")),
|
|
1301
|
+
{
|
|
1302
|
+
title: "Update Query",
|
|
1303
|
+
description: "Update a saved query. At least one mutable field must be provided. Use `queries_publish` to publish a draft.",
|
|
1304
|
+
inputSchema: {
|
|
1305
|
+
id: zod.z.string().describe("Query ID"),
|
|
1306
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
1307
|
+
queryText: zod.z.string().optional().describe("Updated query text"),
|
|
1308
|
+
modelId: zod.z.string().optional().describe("Updated semantic model ID"),
|
|
1309
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
1310
|
+
parameters: JsonObject.optional().describe("Updated parameter JSON"),
|
|
1311
|
+
cacheable: zod.z.boolean().optional().describe("Updated cacheable flag"),
|
|
1312
|
+
cacheTtlSeconds: zod.z.number().int().optional().describe("Updated cache TTL in seconds"),
|
|
1313
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels")
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
async ({
|
|
1317
|
+
id,
|
|
1318
|
+
name,
|
|
1319
|
+
queryText,
|
|
1320
|
+
modelId,
|
|
1321
|
+
description,
|
|
1322
|
+
parameters,
|
|
1323
|
+
cacheable,
|
|
1324
|
+
cacheTtlSeconds,
|
|
1325
|
+
tags
|
|
1326
|
+
}) => {
|
|
1327
|
+
try {
|
|
1328
|
+
const input = {};
|
|
1329
|
+
if (name !== void 0) input.name = name;
|
|
1330
|
+
if (queryText !== void 0) input.queryText = queryText;
|
|
1331
|
+
if (modelId !== void 0) input.modelId = modelId;
|
|
1332
|
+
if (description !== void 0) input.description = description;
|
|
1333
|
+
if (parameters !== void 0) input.parameters = parameters;
|
|
1334
|
+
if (cacheable !== void 0) input.cacheable = cacheable;
|
|
1335
|
+
if (cacheTtlSeconds !== void 0) input.cacheTtlSeconds = cacheTtlSeconds;
|
|
1336
|
+
if (tags !== void 0) input.tags = tags;
|
|
1337
|
+
if (Object.keys(input).length === 0) {
|
|
1338
|
+
return errorResult(
|
|
1339
|
+
"No updates specified \u2014 provide at least one of name/queryText/modelId/description/parameters/cacheable/cacheTtlSeconds/tags"
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
const data = await ctx.gql.workspace.request(UPDATE_QUERY, { id, input });
|
|
1343
|
+
return jsonResult(data.semUpdateQuery);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
ctx.logger.error("semanticfed queries_update failed", { error: String(error) });
|
|
1346
|
+
return errorResult(
|
|
1347
|
+
`Failed to update query: ${error instanceof Error ? error.message : String(error)}`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1352
|
+
server.registerTool(
|
|
1353
|
+
track(mcpToolName(ctx.productName, "queries_publish")),
|
|
1354
|
+
{
|
|
1355
|
+
title: "Publish Query",
|
|
1356
|
+
description: "Publish a draft query (flips status to PUBLISHED). Returns the published query.",
|
|
1357
|
+
inputSchema: { id: zod.z.string().describe("Query ID") }
|
|
1358
|
+
},
|
|
1359
|
+
async ({ id }) => {
|
|
1360
|
+
try {
|
|
1361
|
+
const data = await ctx.gql.workspace.request(PUBLISH_QUERY, { id });
|
|
1362
|
+
return jsonResult(data.semPublishQuery);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
ctx.logger.error("semanticfed queries_publish failed", { error: String(error) });
|
|
1365
|
+
return errorResult(
|
|
1366
|
+
`Failed to publish query: ${error instanceof Error ? error.message : String(error)}`
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
);
|
|
1371
|
+
server.registerTool(
|
|
1372
|
+
track(mcpToolName(ctx.productName, "queries_archive")),
|
|
1373
|
+
{
|
|
1374
|
+
title: "Archive Query",
|
|
1375
|
+
description: "Archive (soft-delete) a saved query by ID. Returns true on success.",
|
|
1376
|
+
inputSchema: { id: zod.z.string().describe("Query ID") }
|
|
1377
|
+
},
|
|
1378
|
+
async ({ id }) => {
|
|
1379
|
+
try {
|
|
1380
|
+
const data = await ctx.gql.workspace.request(
|
|
1381
|
+
ARCHIVE_QUERY,
|
|
1382
|
+
{ id }
|
|
1383
|
+
);
|
|
1384
|
+
return jsonResult({ archived: data.semArchiveQuery });
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
ctx.logger.error("semanticfed queries_archive failed", { error: String(error) });
|
|
1387
|
+
return errorResult(
|
|
1388
|
+
`Failed to archive query: ${error instanceof Error ? error.message : String(error)}`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
server.registerTool(
|
|
1394
|
+
track(mcpToolName(ctx.productName, "queries_run")),
|
|
1395
|
+
{
|
|
1396
|
+
title: "Run Query",
|
|
1397
|
+
description: "Enqueue a federated execution of a saved query. Returns the new run record; poll it with `runs_list`.",
|
|
1398
|
+
inputSchema: { id: zod.z.string().describe("Query ID") }
|
|
1399
|
+
},
|
|
1400
|
+
async ({ id }) => {
|
|
1401
|
+
try {
|
|
1402
|
+
const data = await ctx.gql.workspace.request(RUN_QUERY, { id });
|
|
1403
|
+
return jsonResult(data.semRunQuery);
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
ctx.logger.error("semanticfed queries_run failed", { error: String(error) });
|
|
1406
|
+
return errorResult(
|
|
1407
|
+
`Failed to run query: ${error instanceof Error ? error.message : String(error)}`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
function registerRunTools(server, ctx, track) {
|
|
1414
|
+
server.registerTool(
|
|
1415
|
+
track(mcpToolName(ctx.productName, "query_runs_list")),
|
|
1416
|
+
{
|
|
1417
|
+
title: "List Query Runs",
|
|
1418
|
+
description: "List the execution runs for a saved query, optionally filtered by status, with limit/offset pagination.",
|
|
1419
|
+
inputSchema: {
|
|
1420
|
+
queryId: zod.z.string().describe("Owning query ID"),
|
|
1421
|
+
status: QueryRunStatusEnum.optional().describe("Filter by run status"),
|
|
1422
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1423
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
async ({ queryId, status, limit, offset }) => {
|
|
1427
|
+
try {
|
|
1428
|
+
const data = await ctx.gql.workspace.request(LIST_QUERY_RUNS, {
|
|
1429
|
+
queryId,
|
|
1430
|
+
status,
|
|
1431
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0 }
|
|
1432
|
+
});
|
|
1433
|
+
return jsonResult(data.semListQueryRuns);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
ctx.logger.error("semanticfed query_runs_list failed", { error: String(error) });
|
|
1436
|
+
return errorResult(
|
|
1437
|
+
`Failed to list query runs: ${error instanceof Error ? error.message : String(error)}`
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
function registerPolicyTools(server, ctx, track) {
|
|
1444
|
+
server.registerTool(
|
|
1445
|
+
track(mcpToolName(ctx.productName, "access_policies_list")),
|
|
1446
|
+
{
|
|
1447
|
+
title: "List Access Policies",
|
|
1448
|
+
description: "List access policies, optionally scoped to a specific model and/or entity.",
|
|
1449
|
+
inputSchema: {
|
|
1450
|
+
modelId: zod.z.string().optional().describe("Filter by semantic model ID"),
|
|
1451
|
+
entityId: zod.z.string().optional().describe("Filter by entity ID")
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
async ({ modelId, entityId }) => {
|
|
1455
|
+
try {
|
|
1456
|
+
const data = await ctx.gql.workspace.request(LIST_ACCESS_POLICIES, { modelId, entityId });
|
|
1457
|
+
return jsonResult(data.semListAccessPolicies);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
ctx.logger.error("semanticfed access_policies_list failed", { error: String(error) });
|
|
1460
|
+
return errorResult(
|
|
1461
|
+
`Failed to list access policies: ${error instanceof Error ? error.message : String(error)}`
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
);
|
|
1466
|
+
server.registerTool(
|
|
1467
|
+
track(mcpToolName(ctx.productName, "access_policies_create")),
|
|
1468
|
+
{
|
|
1469
|
+
title: "Create Access Policy",
|
|
1470
|
+
description: "Create a governance policy. `name` and `principal` are required; `effect` defaults to ALLOW and `scope` to ROW. Scope it to a model and/or entity, and supply a `rule` JSON for ROW/COLUMN predicates.",
|
|
1471
|
+
inputSchema: {
|
|
1472
|
+
name: zod.z.string().min(1).describe("Policy display name"),
|
|
1473
|
+
principal: zod.z.string().min(1).describe("Principal the policy applies to (role/group/user expression)"),
|
|
1474
|
+
effect: PolicyEffectEnum.optional().describe("ALLOW or DENY (default ALLOW)"),
|
|
1475
|
+
scope: PolicyScopeEnum.optional().describe("ROW, COLUMN, or ENTITY (default ROW)"),
|
|
1476
|
+
modelId: zod.z.string().optional().describe("Semantic model ID the policy applies to"),
|
|
1477
|
+
entityId: zod.z.string().optional().describe("Entity ID the policy applies to"),
|
|
1478
|
+
description: zod.z.string().optional().describe("Description"),
|
|
1479
|
+
rule: JsonObject.optional().describe("Rule JSON (predicate / column mask)")
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
async ({ name, principal, effect, scope, modelId, entityId, description, rule }) => {
|
|
1483
|
+
try {
|
|
1484
|
+
const input = { name, principal };
|
|
1485
|
+
if (effect !== void 0) input.effect = effect;
|
|
1486
|
+
if (scope !== void 0) input.scope = scope;
|
|
1487
|
+
if (modelId !== void 0) input.modelId = modelId;
|
|
1488
|
+
if (entityId !== void 0) input.entityId = entityId;
|
|
1489
|
+
if (description !== void 0) input.description = description;
|
|
1490
|
+
if (rule !== void 0) input.rule = rule;
|
|
1491
|
+
const data = await ctx.gql.workspace.request(CREATE_ACCESS_POLICY, { input });
|
|
1492
|
+
return jsonResult(data.semCreateAccessPolicy);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
ctx.logger.error("semanticfed access_policies_create failed", { error: String(error) });
|
|
1495
|
+
return errorResult(
|
|
1496
|
+
`Failed to create access policy: ${error instanceof Error ? error.message : String(error)}`
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
);
|
|
1501
|
+
server.registerTool(
|
|
1502
|
+
track(mcpToolName(ctx.productName, "access_policies_set_active")),
|
|
1503
|
+
{
|
|
1504
|
+
title: "Set Access Policy Active",
|
|
1505
|
+
description: "Enable or disable an access policy by ID. Returns the updated policy.",
|
|
1506
|
+
inputSchema: {
|
|
1507
|
+
id: zod.z.string().describe("Access policy ID"),
|
|
1508
|
+
isActive: zod.z.boolean().describe("Whether the policy should be active")
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
async ({ id, isActive }) => {
|
|
1512
|
+
try {
|
|
1513
|
+
const data = await ctx.gql.workspace.request(SET_ACCESS_POLICY_ACTIVE, { id, isActive });
|
|
1514
|
+
return jsonResult(data.semSetAccessPolicyActive);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
ctx.logger.error("semanticfed access_policies_set_active failed", { error: String(error) });
|
|
1517
|
+
return errorResult(
|
|
1518
|
+
`Failed to set access policy active state: ${error instanceof Error ? error.message : String(error)}`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/modules/semanticfed/tools/health.ts
|
|
1526
|
+
function registerHealthTools(server, ctx, track) {
|
|
1527
|
+
server.registerTool(
|
|
1528
|
+
track(mcpToolName(ctx.productName, "health_check")),
|
|
1529
|
+
{
|
|
1530
|
+
title: "SemanticFed Health Check",
|
|
1531
|
+
description: "Public health probe for the SemanticFed subgraph (semHealth). Requires no authentication.",
|
|
1532
|
+
inputSchema: {}
|
|
1533
|
+
},
|
|
1534
|
+
async () => {
|
|
1535
|
+
try {
|
|
1536
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1537
|
+
return jsonResult({ ok: true, status: data.semHealth });
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
ctx.logger.error("semanticfed health failed", { error: String(error) });
|
|
1540
|
+
return errorResult(
|
|
1541
|
+
`SemanticFed health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// src/modules/semanticfed/resources.ts
|
|
1549
|
+
function registerSemanticfedResources(server, ctx, track) {
|
|
1550
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1551
|
+
const connectorsUri = track(mcpResourceUri(ctx.productName, "connectors"));
|
|
1552
|
+
const dataSourcesUri = track(mcpResourceUri(ctx.productName, "data-sources"));
|
|
1553
|
+
const modelsUri = track(mcpResourceUri(ctx.productName, "models"));
|
|
1554
|
+
server.registerResource(
|
|
1555
|
+
"semanticfed-schema",
|
|
1556
|
+
schemaUri,
|
|
1557
|
+
{
|
|
1558
|
+
title: "SemanticFed Domain Schema",
|
|
1559
|
+
description: "Overview of the SemanticFed data-federation domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1560
|
+
mimeType: "text/plain"
|
|
1561
|
+
},
|
|
1562
|
+
async () => {
|
|
1563
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1564
|
+
const schema = `# SemanticFed Domain (Overview)
|
|
1565
|
+
|
|
1566
|
+
The data-federation platform \u2014 unifies databases, warehouses, lakes, and APIs
|
|
1567
|
+
behind one governed semantic layer so teams can query everything in place
|
|
1568
|
+
without moving it. System of record for data sources & connectors, the
|
|
1569
|
+
versioned semantic model, saved federated queries with run history, and access
|
|
1570
|
+
governance.
|
|
1571
|
+
|
|
1572
|
+
## Entities
|
|
1573
|
+
- SemConnector \u2014 a connection-technology registry entry (has data sources)
|
|
1574
|
+
- SemDataSource \u2014 a physical/logical source behind a connector (has connections)
|
|
1575
|
+
- SemConnection \u2014 a recorded health probe for a data source
|
|
1576
|
+
- SemSemanticModel \u2014 the versioned modelling unit (has entities + relationships)
|
|
1577
|
+
- SemEntity \u2014 a table/view/derived entity (has dimensions + metrics)
|
|
1578
|
+
- SemDimension \u2014 a descriptive attribute on an entity
|
|
1579
|
+
- SemMetric \u2014 an aggregated measure on an entity
|
|
1580
|
+
- SemRelationship \u2014 a join between two entities
|
|
1581
|
+
- SemQuery \u2014 a saved federated query (has runs)
|
|
1582
|
+
- SemQueryRun \u2014 an execution of a saved query
|
|
1583
|
+
- SemAccessPolicy \u2014 a row/column/entity governance policy
|
|
1584
|
+
|
|
1585
|
+
## Enums
|
|
1586
|
+
- SemConnectorKind: BIGQUERY, CLICKHOUSE, DATABRICKS, GRAPHQL_API, MONGODB, MYSQL, OTHER, POSTGRES, REDSHIFT, REST_API, S3_PARQUET, SNOWFLAKE
|
|
1587
|
+
- SemDataSourceStatus: DRAFT, CONNECTED, DEGRADED, DISCONNECTED, DISABLED
|
|
1588
|
+
- SemConnectionHealth: HEALTHY, DEGRADED, UNREACHABLE, UNKNOWN
|
|
1589
|
+
- SemModelStatus: DRAFT, PUBLISHED, DEPRECATED, ARCHIVED
|
|
1590
|
+
- SemEntityKind: TABLE, VIEW, DERIVED, EXTERNAL
|
|
1591
|
+
- SemDataType: STRING, INTEGER, DECIMAL, BOOLEAN, DATE, TIMESTAMP, JSON, GEO
|
|
1592
|
+
- SemAggregation: SUM, AVG, COUNT, COUNT_DISTINCT, MIN, MAX, MEDIAN, CUSTOM
|
|
1593
|
+
- SemRelationshipKind: ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANY
|
|
1594
|
+
- SemQueryStatus: DRAFT, PUBLISHED, ARCHIVED
|
|
1595
|
+
- SemQueryRunStatus: QUEUED, PLANNING, RUNNING, SUCCEEDED, FAILED, CANCELLED
|
|
1596
|
+
- SemPolicyEffect: ALLOW, DENY
|
|
1597
|
+
- SemPolicyScope: ROW, COLUMN, ENTITY
|
|
1598
|
+
|
|
1599
|
+
## Tool groups
|
|
1600
|
+
connectors, data_sources, connections, models, entities, dimensions, metrics,
|
|
1601
|
+
relationships, queries, query_runs, access_policies, health. All run against the WORKSPACE
|
|
1602
|
+
gateway (workspace-scoped) except the public health probe.
|
|
1603
|
+
`;
|
|
1604
|
+
return {
|
|
1605
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
server.registerResource(
|
|
1610
|
+
"semanticfed-connectors",
|
|
1611
|
+
connectorsUri,
|
|
1612
|
+
{
|
|
1613
|
+
title: "SemanticFed Connectors",
|
|
1614
|
+
description: "The first 50 connectors in the bound workspace.",
|
|
1615
|
+
mimeType: "application/json"
|
|
1616
|
+
},
|
|
1617
|
+
async () => {
|
|
1618
|
+
ctx.logger.debug(`Resource ${connectorsUri} requested`);
|
|
1619
|
+
try {
|
|
1620
|
+
const data = await ctx.gql.workspace.request(
|
|
1621
|
+
LIST_CONNECTORS,
|
|
1622
|
+
{ pagination: { limit: 50, offset: 0 } }
|
|
1623
|
+
);
|
|
1624
|
+
return {
|
|
1625
|
+
contents: [
|
|
1626
|
+
{
|
|
1627
|
+
uri: connectorsUri,
|
|
1628
|
+
text: JSON.stringify(data.semListConnectors, null, 2),
|
|
1629
|
+
mimeType: "application/json"
|
|
1630
|
+
}
|
|
1631
|
+
]
|
|
1632
|
+
};
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
return {
|
|
1635
|
+
contents: [
|
|
1636
|
+
{
|
|
1637
|
+
uri: connectorsUri,
|
|
1638
|
+
text: JSON.stringify({
|
|
1639
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1640
|
+
}),
|
|
1641
|
+
mimeType: "application/json"
|
|
1642
|
+
}
|
|
1643
|
+
]
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
);
|
|
1648
|
+
server.registerResource(
|
|
1649
|
+
"semanticfed-data-sources",
|
|
1650
|
+
dataSourcesUri,
|
|
1651
|
+
{
|
|
1652
|
+
title: "SemanticFed Data Sources",
|
|
1653
|
+
description: "The first 50 data sources in the bound workspace.",
|
|
1654
|
+
mimeType: "application/json"
|
|
1655
|
+
},
|
|
1656
|
+
async () => {
|
|
1657
|
+
ctx.logger.debug(`Resource ${dataSourcesUri} requested`);
|
|
1658
|
+
try {
|
|
1659
|
+
const data = await ctx.gql.workspace.request(
|
|
1660
|
+
LIST_DATA_SOURCES,
|
|
1661
|
+
{ pagination: { limit: 50, offset: 0 } }
|
|
1662
|
+
);
|
|
1663
|
+
return {
|
|
1664
|
+
contents: [
|
|
1665
|
+
{
|
|
1666
|
+
uri: dataSourcesUri,
|
|
1667
|
+
text: JSON.stringify(data.semListDataSources, null, 2),
|
|
1668
|
+
mimeType: "application/json"
|
|
1669
|
+
}
|
|
1670
|
+
]
|
|
1671
|
+
};
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
return {
|
|
1674
|
+
contents: [
|
|
1675
|
+
{
|
|
1676
|
+
uri: dataSourcesUri,
|
|
1677
|
+
text: JSON.stringify({
|
|
1678
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1679
|
+
}),
|
|
1680
|
+
mimeType: "application/json"
|
|
1681
|
+
}
|
|
1682
|
+
]
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
server.registerResource(
|
|
1688
|
+
"semanticfed-models",
|
|
1689
|
+
modelsUri,
|
|
1690
|
+
{
|
|
1691
|
+
title: "SemanticFed Semantic Models",
|
|
1692
|
+
description: "The first 50 semantic models in the bound workspace.",
|
|
1693
|
+
mimeType: "application/json"
|
|
1694
|
+
},
|
|
1695
|
+
async () => {
|
|
1696
|
+
ctx.logger.debug(`Resource ${modelsUri} requested`);
|
|
1697
|
+
try {
|
|
1698
|
+
const data = await ctx.gql.workspace.request(LIST_MODELS, {
|
|
1699
|
+
pagination: { limit: 50, offset: 0 }
|
|
1700
|
+
});
|
|
1701
|
+
return {
|
|
1702
|
+
contents: [
|
|
1703
|
+
{
|
|
1704
|
+
uri: modelsUri,
|
|
1705
|
+
text: JSON.stringify(data.semListModels, null, 2),
|
|
1706
|
+
mimeType: "application/json"
|
|
1707
|
+
}
|
|
1708
|
+
]
|
|
1709
|
+
};
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
return {
|
|
1712
|
+
contents: [
|
|
1713
|
+
{
|
|
1714
|
+
uri: modelsUri,
|
|
1715
|
+
text: JSON.stringify({
|
|
1716
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1717
|
+
}),
|
|
1718
|
+
mimeType: "application/json"
|
|
1719
|
+
}
|
|
1720
|
+
]
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
function registerSemanticfedPrompts(server, ctx, track) {
|
|
1727
|
+
const p = ctx.productName;
|
|
1728
|
+
const modellingName = track(mcpPromptName(p, "model-build"));
|
|
1729
|
+
const federationName = track(mcpPromptName(p, "query-federation"));
|
|
1730
|
+
server.registerPrompt(
|
|
1731
|
+
modellingName,
|
|
1732
|
+
{
|
|
1733
|
+
title: "Build Semantic Model",
|
|
1734
|
+
description: "Guide for building a SemanticFed semantic model end to end: register a connector, attach a data source, create the model, add entities with dimensions/metrics, define relationships, and publish.",
|
|
1735
|
+
argsSchema: {
|
|
1736
|
+
name: zod.z.string().optional().describe("Semantic model name"),
|
|
1737
|
+
connectorKind: zod.z.string().optional().describe("Connector technology (e.g. POSTGRES)")
|
|
1738
|
+
}
|
|
1739
|
+
},
|
|
1740
|
+
({ name, connectorKind }) => {
|
|
1741
|
+
ctx.logger.debug(`Prompt ${modellingName} called`, { name, connectorKind });
|
|
1742
|
+
const nameNote = name ? `Use the model name "${name}".` : "Ask the user for the model name.";
|
|
1743
|
+
const kindNote = connectorKind ? `Use connector kind ${connectorKind}.` : "Help the user pick a SemConnectorKind (e.g. POSTGRES, SNOWFLAKE, BIGQUERY).";
|
|
1744
|
+
return {
|
|
1745
|
+
messages: [
|
|
1746
|
+
{
|
|
1747
|
+
role: "user",
|
|
1748
|
+
content: {
|
|
1749
|
+
type: "text",
|
|
1750
|
+
text: `Help me build a new SemanticFed semantic model.
|
|
1751
|
+
|
|
1752
|
+
1. **Register Connector**: Use ${p}_connectors_create. ${kindNote}
|
|
1753
|
+
2. **Attach Data Source**: Use ${p}_data_sources_create with the new connectorId, an endpoint JSON, and a secretRef for credentials; then ${p}_data_sources_transition to CONNECTED once reachable (optionally record a probe with ${p}_connections_record).
|
|
1754
|
+
3. **Create Model**: Use ${p}_models_create. ${nameNote}
|
|
1755
|
+
4. **Add Entities**: Use ${p}_entities_create (modelId + kind TABLE/VIEW/DERIVED) for each table/view; bind to the dataSourceId.
|
|
1756
|
+
5. **Dimensions + Metrics**: Use ${p}_dimensions_create and ${p}_metrics_create on each entity.
|
|
1757
|
+
6. **Relationships**: Use ${p}_relationships_create to join entities (fromEntityId/fromField \u2192 toEntityId/toField, kind MANY_TO_ONE etc.).
|
|
1758
|
+
7. **Publish**: Use ${p}_models_publish to bump the version and flip status to PUBLISHED.
|
|
1759
|
+
|
|
1760
|
+
Show results at each step and confirm before publishing.`
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
]
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
);
|
|
1767
|
+
server.registerPrompt(
|
|
1768
|
+
federationName,
|
|
1769
|
+
{
|
|
1770
|
+
title: "Federated Query",
|
|
1771
|
+
description: "Workflow to author a saved federated query against a published model, govern it with an access policy, publish it, execute it, and inspect run results.",
|
|
1772
|
+
argsSchema: {
|
|
1773
|
+
modelId: zod.z.string().optional().describe("Published model ID to query")
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1776
|
+
({ modelId }) => {
|
|
1777
|
+
ctx.logger.debug(`Prompt ${federationName} called`, { modelId });
|
|
1778
|
+
const modelNote = modelId ? `Query against model "${modelId}".` : `First list published models with ${p}_models_list (status=PUBLISHED) and pick one.`;
|
|
1779
|
+
return {
|
|
1780
|
+
messages: [
|
|
1781
|
+
{
|
|
1782
|
+
role: "user",
|
|
1783
|
+
content: {
|
|
1784
|
+
type: "text",
|
|
1785
|
+
text: `Run a SemanticFed federated query workflow. ${modelNote}
|
|
1786
|
+
|
|
1787
|
+
1. **Author**: Use ${p}_queries_create with the modelId, a queryText, and optional parameters; set cacheable + cacheTtlSeconds.
|
|
1788
|
+
2. **Govern**: Use ${p}_access_policies_create to add a row/column access policy (principal, effect, scope) and ${p}_access_policies_set_active to enable it.
|
|
1789
|
+
3. **Publish**: Use ${p}_queries_publish to move the query to PUBLISHED.
|
|
1790
|
+
4. **Execute**: Use ${p}_queries_run to enqueue a federated execution; it returns a run record.
|
|
1791
|
+
5. **Inspect**: Poll ${p}_query_runs_list (filter by status) to watch QUEUED \u2192 PLANNING \u2192 RUNNING \u2192 SUCCEEDED and read rowCount / sourcesTouched / planSummary.
|
|
1792
|
+
|
|
1793
|
+
Summarize what needs attention after each step.`
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
]
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/modules/semanticfed/index.ts
|
|
1803
|
+
var semanticfedModule = {
|
|
1804
|
+
id: "semanticfed",
|
|
1805
|
+
version: "0.0.1",
|
|
1806
|
+
displayName: "SemanticFed",
|
|
1807
|
+
dependsOn: ["auth"],
|
|
1808
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
1809
|
+
register(server, ctx) {
|
|
1810
|
+
const tools = [];
|
|
1811
|
+
const resources = [];
|
|
1812
|
+
const prompts = [];
|
|
1813
|
+
const trackTool = (n) => {
|
|
1814
|
+
tools.push(n);
|
|
1815
|
+
return n;
|
|
1816
|
+
};
|
|
1817
|
+
const trackResource = (uri) => {
|
|
1818
|
+
resources.push(uri);
|
|
1819
|
+
return uri;
|
|
1820
|
+
};
|
|
1821
|
+
const trackPrompt = (n) => {
|
|
1822
|
+
prompts.push(n);
|
|
1823
|
+
return n;
|
|
1824
|
+
};
|
|
1825
|
+
registerConnectorTools(server, ctx, trackTool);
|
|
1826
|
+
registerDataSourceTools(server, ctx, trackTool);
|
|
1827
|
+
registerConnectionTools(server, ctx, trackTool);
|
|
1828
|
+
registerModelTools(server, ctx, trackTool);
|
|
1829
|
+
registerEntityTools(server, ctx, trackTool);
|
|
1830
|
+
registerDimensionTools(server, ctx, trackTool);
|
|
1831
|
+
registerMetricTools(server, ctx, trackTool);
|
|
1832
|
+
registerRelationshipTools(server, ctx, trackTool);
|
|
1833
|
+
registerQueryTools(server, ctx, trackTool);
|
|
1834
|
+
registerRunTools(server, ctx, trackTool);
|
|
1835
|
+
registerPolicyTools(server, ctx, trackTool);
|
|
1836
|
+
registerHealthTools(server, ctx, trackTool);
|
|
1837
|
+
registerSemanticfedResources(server, ctx, trackResource);
|
|
1838
|
+
registerSemanticfedPrompts(server, ctx, trackPrompt);
|
|
1839
|
+
return { tools, resources, prompts };
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
var semanticfed_default = semanticfedModule;
|
|
1843
|
+
|
|
1844
|
+
module.exports = semanticfed_default;
|
|
1845
|
+
//# sourceMappingURL=index.cjs.map
|
|
1846
|
+
//# sourceMappingURL=index.cjs.map
|