@runcontext/mcp 0.1.1 → 0.2.1
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/LICENSE +21 -0
- package/dist/index.cjs +386 -354
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -54
- package/dist/index.d.ts +75 -54
- package/dist/index.mjs +398 -366
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -10
package/dist/index.mjs
CHANGED
|
@@ -1,423 +1,455 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
|
-
import { McpServer
|
|
3
|
-
import {
|
|
2
|
+
import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { compile, emitManifest, loadConfig } from "@runcontext/core";
|
|
4
5
|
|
|
5
6
|
// src/resources/manifest.ts
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
text: JSON.stringify(manifest, null, 2)
|
|
13
|
-
}
|
|
14
|
-
]
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// src/resources/concept.ts
|
|
19
|
-
function readConcept(manifest, id) {
|
|
20
|
-
const concept = manifest.concepts.find((c) => c.id === id);
|
|
21
|
-
if (!concept) {
|
|
22
|
-
return {
|
|
7
|
+
function registerManifestResource(server, manifest) {
|
|
8
|
+
server.resource(
|
|
9
|
+
"manifest",
|
|
10
|
+
"context://manifest",
|
|
11
|
+
{ description: "Full ContextKit manifest JSON (models, governance, rules, lineage, terms, owners, tiers)" },
|
|
12
|
+
async (uri) => ({
|
|
23
13
|
contents: [
|
|
24
14
|
{
|
|
25
|
-
uri:
|
|
15
|
+
uri: uri.href,
|
|
26
16
|
mimeType: "application/json",
|
|
27
|
-
text: JSON.stringify(
|
|
17
|
+
text: JSON.stringify(manifest, null, 2)
|
|
28
18
|
}
|
|
29
19
|
]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
contents: [
|
|
34
|
-
{
|
|
35
|
-
uri: `context://concept/${id}`,
|
|
36
|
-
mimeType: "application/json",
|
|
37
|
-
text: JSON.stringify(concept, null, 2)
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function listConcepts(manifest) {
|
|
43
|
-
return {
|
|
44
|
-
resources: manifest.concepts.map((c) => ({
|
|
45
|
-
uri: `context://concept/${c.id}`,
|
|
46
|
-
name: c.id,
|
|
47
|
-
description: c.definition,
|
|
48
|
-
mimeType: "application/json"
|
|
49
|
-
}))
|
|
50
|
-
};
|
|
20
|
+
})
|
|
21
|
+
);
|
|
51
22
|
}
|
|
52
23
|
|
|
53
|
-
// src/resources/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
contents: [
|
|
59
|
-
{
|
|
60
|
-
uri: `context://product/${id}`,
|
|
61
|
-
mimeType: "application/json",
|
|
62
|
-
text: JSON.stringify({ error: `Product not found: ${id}` })
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
};
|
|
66
|
-
}
|
|
24
|
+
// src/resources/model.ts
|
|
25
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
26
|
+
function buildModelView(name, manifest) {
|
|
27
|
+
const model = manifest.models[name];
|
|
28
|
+
if (!model) return null;
|
|
67
29
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
]
|
|
30
|
+
model,
|
|
31
|
+
governance: manifest.governance[name] ?? null,
|
|
32
|
+
rules: manifest.rules[name] ?? null,
|
|
33
|
+
lineage: manifest.lineage[name] ?? null,
|
|
34
|
+
tier: manifest.tiers[name] ?? null
|
|
75
35
|
};
|
|
76
36
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
37
|
+
function registerModelResource(server, manifest) {
|
|
38
|
+
server.resource(
|
|
39
|
+
"model",
|
|
40
|
+
new ResourceTemplate("context://model/{name}", {
|
|
41
|
+
list: async () => ({
|
|
42
|
+
resources: Object.keys(manifest.models).map((name) => ({
|
|
43
|
+
uri: `context://model/${name}`,
|
|
44
|
+
name,
|
|
45
|
+
description: manifest.models[name]?.description ?? `Model: ${name}`
|
|
46
|
+
}))
|
|
47
|
+
})
|
|
48
|
+
}),
|
|
49
|
+
{ description: "OSI semantic model merged with governance, rules, lineage, and tier" },
|
|
50
|
+
async (uri, { name }) => {
|
|
51
|
+
const modelName = String(name);
|
|
52
|
+
const view = buildModelView(modelName, manifest);
|
|
53
|
+
if (!view) {
|
|
54
|
+
throw new Error(`Model '${modelName}' not found`);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
contents: [
|
|
58
|
+
{
|
|
59
|
+
uri: uri.href,
|
|
60
|
+
mimeType: "application/json",
|
|
61
|
+
text: JSON.stringify(view, null, 2)
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
);
|
|
86
67
|
}
|
|
87
68
|
|
|
88
|
-
// src/resources/
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
69
|
+
// src/resources/glossary.ts
|
|
70
|
+
function registerGlossaryResource(server, manifest) {
|
|
71
|
+
server.resource(
|
|
72
|
+
"glossary",
|
|
73
|
+
"context://glossary",
|
|
74
|
+
{ description: "All ContextKit glossary terms with definitions, synonyms, and mappings" },
|
|
75
|
+
async (uri) => ({
|
|
93
76
|
contents: [
|
|
94
77
|
{
|
|
95
|
-
uri:
|
|
78
|
+
uri: uri.href,
|
|
96
79
|
mimeType: "application/json",
|
|
97
|
-
text: JSON.stringify(
|
|
80
|
+
text: JSON.stringify(manifest.terms, null, 2)
|
|
98
81
|
}
|
|
99
82
|
]
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
contents: [
|
|
104
|
-
{
|
|
105
|
-
uri: `context://policy/${id}`,
|
|
106
|
-
mimeType: "application/json",
|
|
107
|
-
text: JSON.stringify(policy, null, 2)
|
|
108
|
-
}
|
|
109
|
-
]
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function listPolicies(manifest) {
|
|
113
|
-
return {
|
|
114
|
-
resources: manifest.policies.map((p) => ({
|
|
115
|
-
uri: `context://policy/${p.id}`,
|
|
116
|
-
name: p.id,
|
|
117
|
-
description: p.description,
|
|
118
|
-
mimeType: "application/json"
|
|
119
|
-
}))
|
|
120
|
-
};
|
|
83
|
+
})
|
|
84
|
+
);
|
|
121
85
|
}
|
|
122
86
|
|
|
123
|
-
// src/resources/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
87
|
+
// src/resources/tier.ts
|
|
88
|
+
import { ResourceTemplate as ResourceTemplate2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
89
|
+
function registerTierResource(server, manifest) {
|
|
90
|
+
server.resource(
|
|
91
|
+
"tier",
|
|
92
|
+
new ResourceTemplate2("context://tier/{name}", {
|
|
93
|
+
list: async () => ({
|
|
94
|
+
resources: Object.keys(manifest.tiers).map((name) => ({
|
|
95
|
+
uri: `context://tier/${name}`,
|
|
96
|
+
name,
|
|
97
|
+
description: `Tier scorecard for model: ${name} (${manifest.tiers[name]?.tier ?? "unknown"})`
|
|
98
|
+
}))
|
|
99
|
+
})
|
|
100
|
+
}),
|
|
101
|
+
{ description: "Tier scorecard for a model (bronze/silver/gold checks and results)" },
|
|
102
|
+
async (uri, { name }) => {
|
|
103
|
+
const modelName = String(name);
|
|
104
|
+
const tier = manifest.tiers[modelName];
|
|
105
|
+
if (!tier) {
|
|
106
|
+
throw new Error(`Tier data for model '${modelName}' not found`);
|
|
137
107
|
}
|
|
138
|
-
|
|
139
|
-
|
|
108
|
+
return {
|
|
109
|
+
contents: [
|
|
110
|
+
{
|
|
111
|
+
uri: uri.href,
|
|
112
|
+
mimeType: "application/json",
|
|
113
|
+
text: JSON.stringify(tier, null, 2)
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
);
|
|
140
119
|
}
|
|
141
120
|
|
|
142
121
|
// src/tools/search.ts
|
|
143
|
-
|
|
122
|
+
import { z } from "zod";
|
|
123
|
+
function searchManifest(manifest, query) {
|
|
124
|
+
const results = [];
|
|
144
125
|
const q = query.toLowerCase();
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
description: p.description,
|
|
162
|
-
tags: p.tags
|
|
163
|
-
})),
|
|
164
|
-
...manifest.entities.map((e) => ({
|
|
165
|
-
kind: "entity",
|
|
166
|
-
id: e.id,
|
|
167
|
-
definition: e.definition,
|
|
168
|
-
tags: e.tags
|
|
169
|
-
})),
|
|
170
|
-
...manifest.terms.map((t) => ({
|
|
171
|
-
kind: "term",
|
|
172
|
-
id: t.id,
|
|
173
|
-
definition: t.definition,
|
|
174
|
-
tags: t.tags
|
|
175
|
-
}))
|
|
176
|
-
];
|
|
177
|
-
const matches = items.filter((item) => {
|
|
178
|
-
if (item.id.toLowerCase().includes(q)) return true;
|
|
179
|
-
if (item.definition && item.definition.toLowerCase().includes(q)) return true;
|
|
180
|
-
if (item.description && item.description.toLowerCase().includes(q)) return true;
|
|
181
|
-
if (item.tags && item.tags.some((tag) => tag.toLowerCase().includes(q))) return true;
|
|
182
|
-
return false;
|
|
183
|
-
});
|
|
184
|
-
return {
|
|
185
|
-
content: [
|
|
186
|
-
{
|
|
187
|
-
type: "text",
|
|
188
|
-
text: JSON.stringify({ query, resultCount: matches.length, results: matches }, null, 2)
|
|
126
|
+
for (const [name, model] of Object.entries(manifest.models)) {
|
|
127
|
+
if (name.toLowerCase().includes(q) || model.description?.toLowerCase().includes(q)) {
|
|
128
|
+
results.push({
|
|
129
|
+
type: "model",
|
|
130
|
+
name,
|
|
131
|
+
description: model.description
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
for (const ds of model.datasets ?? []) {
|
|
135
|
+
if (ds.name.toLowerCase().includes(q) || ds.description?.toLowerCase().includes(q)) {
|
|
136
|
+
results.push({
|
|
137
|
+
type: "dataset",
|
|
138
|
+
name: ds.name,
|
|
139
|
+
description: ds.description,
|
|
140
|
+
model: name
|
|
141
|
+
});
|
|
189
142
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
type: "text",
|
|
202
|
-
text: JSON.stringify({ error: `Node not found: ${id}` }, null, 2)
|
|
143
|
+
if (ds.fields) {
|
|
144
|
+
for (const field of ds.fields) {
|
|
145
|
+
if (field.name.toLowerCase().includes(q) || field.description?.toLowerCase().includes(q) || field.label?.toLowerCase().includes(q)) {
|
|
146
|
+
results.push({
|
|
147
|
+
type: "field",
|
|
148
|
+
name: field.name,
|
|
149
|
+
description: field.description,
|
|
150
|
+
model: name,
|
|
151
|
+
dataset: ds.name
|
|
152
|
+
});
|
|
153
|
+
}
|
|
203
154
|
}
|
|
204
|
-
]
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
let node;
|
|
208
|
-
switch (index.kind) {
|
|
209
|
-
case "concept":
|
|
210
|
-
node = manifest.concepts[index.index];
|
|
211
|
-
break;
|
|
212
|
-
case "product":
|
|
213
|
-
node = manifest.products[index.index];
|
|
214
|
-
break;
|
|
215
|
-
case "policy":
|
|
216
|
-
node = manifest.policies[index.index];
|
|
217
|
-
break;
|
|
218
|
-
case "entity":
|
|
219
|
-
node = manifest.entities[index.index];
|
|
220
|
-
break;
|
|
221
|
-
case "term":
|
|
222
|
-
node = manifest.terms[index.index];
|
|
223
|
-
break;
|
|
224
|
-
case "owner":
|
|
225
|
-
node = manifest.owners[index.index];
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
if (!node) {
|
|
229
|
-
return {
|
|
230
|
-
content: [
|
|
231
|
-
{
|
|
232
|
-
type: "text",
|
|
233
|
-
text: JSON.stringify({ error: `Node data not found for: ${id}` }, null, 2)
|
|
234
|
-
}
|
|
235
|
-
]
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
const dependencies = [];
|
|
239
|
-
const dependsOn = node.dependsOn;
|
|
240
|
-
if (dependsOn) {
|
|
241
|
-
for (const depId of dependsOn) {
|
|
242
|
-
const depIndex = manifest.indexes.byId[depId];
|
|
243
|
-
if (depIndex) {
|
|
244
|
-
dependencies.push({ id: depId, kind: depIndex.kind });
|
|
245
155
|
}
|
|
246
156
|
}
|
|
247
157
|
}
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
158
|
+
for (const [id, term] of Object.entries(manifest.terms)) {
|
|
159
|
+
if (id.toLowerCase().includes(q) || term.definition.toLowerCase().includes(q) || term.synonyms?.some((s) => s.toLowerCase().includes(q))) {
|
|
160
|
+
results.push({
|
|
161
|
+
type: "term",
|
|
162
|
+
name: id,
|
|
163
|
+
description: term.definition
|
|
164
|
+
});
|
|
252
165
|
}
|
|
253
166
|
}
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
if (applies) {
|
|
263
|
-
applicablePolicies.push({ id: policy.id, description: policy.description });
|
|
167
|
+
for (const [id, owner] of Object.entries(manifest.owners)) {
|
|
168
|
+
if (id.toLowerCase().includes(q) || owner.display_name.toLowerCase().includes(q) || owner.description?.toLowerCase().includes(q)) {
|
|
169
|
+
results.push({
|
|
170
|
+
type: "owner",
|
|
171
|
+
name: id,
|
|
172
|
+
description: owner.display_name
|
|
173
|
+
});
|
|
264
174
|
}
|
|
265
175
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
function registerSearchTool(server, manifest) {
|
|
179
|
+
server.tool(
|
|
180
|
+
"context_search",
|
|
181
|
+
"Search across all ContextKit nodes (models, datasets, fields, terms, owners) by keyword",
|
|
182
|
+
{ query: z.string().describe("Keyword to search for") },
|
|
183
|
+
async ({ query }) => {
|
|
184
|
+
const results = searchManifest(manifest, query);
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: JSON.stringify(results, null, 2)
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/tools/explain.ts
|
|
198
|
+
import { z as z2 } from "zod";
|
|
199
|
+
function explainModel(name, manifest) {
|
|
200
|
+
const model = manifest.models[name];
|
|
201
|
+
if (!model) return null;
|
|
202
|
+
const governance = manifest.governance[name] ?? null;
|
|
203
|
+
const rules = manifest.rules[name] ?? null;
|
|
204
|
+
const lineage = manifest.lineage[name] ?? null;
|
|
205
|
+
const tier = manifest.tiers[name] ?? null;
|
|
206
|
+
const ownerKey = governance?.owner;
|
|
207
|
+
const owner = ownerKey ? manifest.owners[ownerKey] ?? null : null;
|
|
208
|
+
const modelTags = governance?.tags ?? [];
|
|
209
|
+
const relatedTerms = [];
|
|
210
|
+
for (const [, term] of Object.entries(manifest.terms)) {
|
|
211
|
+
const termTags = term.tags ?? [];
|
|
212
|
+
const hasOverlap = modelTags.some((t) => termTags.includes(t));
|
|
213
|
+
const mapsToModel = term.maps_to?.some((m) => m === name);
|
|
214
|
+
if (hasOverlap || mapsToModel) {
|
|
215
|
+
relatedTerms.push(term);
|
|
216
|
+
}
|
|
270
217
|
}
|
|
271
|
-
const result = {
|
|
272
|
-
kind: index.kind,
|
|
273
|
-
node,
|
|
274
|
-
dependencies,
|
|
275
|
-
dependents,
|
|
276
|
-
applicablePolicies,
|
|
277
|
-
owner: ownerInfo ?? null
|
|
278
|
-
};
|
|
279
218
|
return {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
219
|
+
model,
|
|
220
|
+
governance,
|
|
221
|
+
rules,
|
|
222
|
+
lineage,
|
|
223
|
+
tier,
|
|
224
|
+
owner,
|
|
225
|
+
relatedTerms
|
|
286
226
|
};
|
|
287
227
|
}
|
|
228
|
+
function registerExplainTool(server, manifest) {
|
|
229
|
+
server.tool(
|
|
230
|
+
"context_explain",
|
|
231
|
+
"Deep lookup of a model with all related governance, rules, lineage, tier, owner, and glossary terms",
|
|
232
|
+
{ model: z2.string().describe("Name of the model to explain") },
|
|
233
|
+
async ({ model }) => {
|
|
234
|
+
const result = explainModel(model, manifest);
|
|
235
|
+
if (!result) {
|
|
236
|
+
return {
|
|
237
|
+
content: [
|
|
238
|
+
{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: JSON.stringify({ error: `Model '${model}' not found` })
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: JSON.stringify(result, null, 2)
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
}
|
|
288
256
|
|
|
289
257
|
// src/tools/validate.ts
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
contextDir,
|
|
296
|
-
config: {}
|
|
297
|
-
});
|
|
298
|
-
const engine = new LintEngine();
|
|
299
|
-
for (const rule of ALL_RULES) {
|
|
300
|
-
engine.register(rule);
|
|
301
|
-
}
|
|
302
|
-
const lintDiagnostics = engine.run(compileResult.graph);
|
|
303
|
-
const allDiagnostics = [...compileResult.diagnostics, ...lintDiagnostics];
|
|
304
|
-
const summary = {
|
|
305
|
-
contextDir,
|
|
306
|
-
compileDiagnostics: compileResult.diagnostics.length,
|
|
307
|
-
lintDiagnostics: lintDiagnostics.length,
|
|
308
|
-
totalDiagnostics: allDiagnostics.length,
|
|
309
|
-
errors: allDiagnostics.filter((d) => d.severity === "error").length,
|
|
310
|
-
warnings: allDiagnostics.filter((d) => d.severity === "warning").length,
|
|
311
|
-
diagnostics: allDiagnostics
|
|
312
|
-
};
|
|
313
|
-
return {
|
|
314
|
-
content: [
|
|
315
|
-
{
|
|
316
|
-
type: "text",
|
|
317
|
-
text: JSON.stringify(summary, null, 2)
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
};
|
|
321
|
-
} catch (err) {
|
|
322
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
323
|
-
return {
|
|
324
|
-
content: [
|
|
325
|
-
{
|
|
326
|
-
type: "text",
|
|
327
|
-
text: JSON.stringify({ error: `Validation failed: ${message}` }, null, 2)
|
|
328
|
-
}
|
|
329
|
-
],
|
|
330
|
-
isError: true
|
|
331
|
-
};
|
|
258
|
+
import { LintEngine, ALL_RULES } from "@runcontext/core";
|
|
259
|
+
function validateGraph(graph) {
|
|
260
|
+
const engine = new LintEngine();
|
|
261
|
+
for (const rule of ALL_RULES) {
|
|
262
|
+
engine.register(rule);
|
|
332
263
|
}
|
|
264
|
+
const diagnostics = engine.run(graph);
|
|
265
|
+
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
266
|
+
const warnings = diagnostics.filter((d) => d.severity === "warning").length;
|
|
267
|
+
return {
|
|
268
|
+
totalDiagnostics: diagnostics.length,
|
|
269
|
+
errors,
|
|
270
|
+
warnings,
|
|
271
|
+
diagnostics
|
|
272
|
+
};
|
|
333
273
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
274
|
+
function registerValidateTool(server, graph) {
|
|
275
|
+
server.tool(
|
|
276
|
+
"context_validate",
|
|
277
|
+
"Run ContextKit linter against the context graph and return diagnostics",
|
|
278
|
+
{},
|
|
279
|
+
async () => {
|
|
280
|
+
const result = validateGraph(graph);
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: JSON.stringify(result, null, 2)
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
};
|
|
347
289
|
}
|
|
348
290
|
);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
() => readGlossary(manifest)
|
|
360
|
-
);
|
|
361
|
-
server.resource(
|
|
362
|
-
"concept",
|
|
363
|
-
new ResourceTemplate("context://concept/{id}", {
|
|
364
|
-
list: () => listConcepts(manifest)
|
|
365
|
-
}),
|
|
366
|
-
{ description: "A single concept by ID", mimeType: "application/json" },
|
|
367
|
-
(uri, variables) => readConcept(manifest, String(variables.id))
|
|
368
|
-
);
|
|
369
|
-
server.resource(
|
|
370
|
-
"product",
|
|
371
|
-
new ResourceTemplate("context://product/{id}", {
|
|
372
|
-
list: () => listProducts(manifest)
|
|
373
|
-
}),
|
|
374
|
-
{ description: "A single product by ID", mimeType: "application/json" },
|
|
375
|
-
(uri, variables) => readProduct(manifest, String(variables.id))
|
|
376
|
-
);
|
|
377
|
-
server.resource(
|
|
378
|
-
"policy",
|
|
379
|
-
new ResourceTemplate("context://policy/{id}", {
|
|
380
|
-
list: () => listPolicies(manifest)
|
|
381
|
-
}),
|
|
382
|
-
{ description: "A single policy by ID", mimeType: "application/json" },
|
|
383
|
-
(uri, variables) => readPolicy(manifest, String(variables.id))
|
|
384
|
-
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/tools/tier.ts
|
|
294
|
+
import { z as z3 } from "zod";
|
|
295
|
+
import { computeTier } from "@runcontext/core";
|
|
296
|
+
function computeModelTier(modelName, graph) {
|
|
297
|
+
if (!graph.models.has(modelName)) return null;
|
|
298
|
+
return computeTier(modelName, graph);
|
|
299
|
+
}
|
|
300
|
+
function registerTierTool(server, graph) {
|
|
385
301
|
server.tool(
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
{
|
|
389
|
-
({
|
|
302
|
+
"context_tier",
|
|
303
|
+
"Compute the metadata tier (none/bronze/silver/gold) for a model with detailed check results",
|
|
304
|
+
{ model: z3.string().describe("Name of the model to tier") },
|
|
305
|
+
async ({ model }) => {
|
|
306
|
+
const result = computeModelTier(model, graph);
|
|
307
|
+
if (!result) {
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify({ error: `Model '${model}' not found` })
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{
|
|
320
|
+
type: "text",
|
|
321
|
+
text: JSON.stringify(result, null, 2)
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
390
326
|
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/tools/golden-query.ts
|
|
330
|
+
import { z as z4 } from "zod";
|
|
331
|
+
function findGoldenQueries(manifest, question) {
|
|
332
|
+
const stopWords = /* @__PURE__ */ new Set(["a", "an", "the", "is", "in", "on", "at", "to", "of", "for", "and", "or", "not"]);
|
|
333
|
+
const qWords = question.toLowerCase().split(/\s+/).filter((w) => w.length > 0 && !stopWords.has(w));
|
|
334
|
+
const matches = [];
|
|
335
|
+
for (const [modelName, rules] of Object.entries(manifest.rules)) {
|
|
336
|
+
if (!rules.golden_queries) continue;
|
|
337
|
+
for (const gq of rules.golden_queries) {
|
|
338
|
+
const gqWords = gq.question.toLowerCase().split(/\s+/);
|
|
339
|
+
const overlap = qWords.filter((w) => gqWords.some((gw) => gw.includes(w))).length;
|
|
340
|
+
if (overlap > 0) {
|
|
341
|
+
matches.push({
|
|
342
|
+
model: modelName,
|
|
343
|
+
query: gq,
|
|
344
|
+
score: overlap / Math.max(qWords.length, 1)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
matches.sort((a, b) => b.score - a.score);
|
|
350
|
+
return matches;
|
|
351
|
+
}
|
|
352
|
+
function registerGoldenQueryTool(server, manifest) {
|
|
391
353
|
server.tool(
|
|
392
|
-
"
|
|
393
|
-
"
|
|
394
|
-
{
|
|
395
|
-
({
|
|
354
|
+
"context_golden_query",
|
|
355
|
+
"Find golden SQL queries that match a natural-language question",
|
|
356
|
+
{ question: z4.string().describe("Natural-language question to match against golden queries") },
|
|
357
|
+
async ({ question }) => {
|
|
358
|
+
const results = findGoldenQueries(manifest, question);
|
|
359
|
+
return {
|
|
360
|
+
content: [
|
|
361
|
+
{
|
|
362
|
+
type: "text",
|
|
363
|
+
text: JSON.stringify(results, null, 2)
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
};
|
|
367
|
+
}
|
|
396
368
|
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/tools/guardrails.ts
|
|
372
|
+
import { z as z5 } from "zod";
|
|
373
|
+
function findGuardrails(manifest, tables) {
|
|
374
|
+
const matches = [];
|
|
375
|
+
const tableSet = new Set(tables.map((t) => t.toLowerCase()));
|
|
376
|
+
for (const [modelName, rules] of Object.entries(manifest.rules)) {
|
|
377
|
+
if (!rules.guardrail_filters) continue;
|
|
378
|
+
for (const gf of rules.guardrail_filters) {
|
|
379
|
+
const applies = !gf.tables || gf.tables.length === 0 || gf.tables.some((t) => tableSet.has(t.toLowerCase()));
|
|
380
|
+
if (applies) {
|
|
381
|
+
matches.push({
|
|
382
|
+
model: modelName,
|
|
383
|
+
filter: gf
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return matches;
|
|
389
|
+
}
|
|
390
|
+
function registerGuardrailsTool(server, manifest) {
|
|
397
391
|
server.tool(
|
|
398
|
-
"
|
|
399
|
-
"
|
|
400
|
-
{
|
|
401
|
-
|
|
392
|
+
"context_guardrails",
|
|
393
|
+
"Return guardrail filters that apply to the specified tables",
|
|
394
|
+
{
|
|
395
|
+
tables: z5.array(z5.string()).describe("List of table names to check guardrails for")
|
|
396
|
+
},
|
|
397
|
+
async ({ tables }) => {
|
|
398
|
+
const results = findGuardrails(manifest, tables);
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: "text",
|
|
403
|
+
text: JSON.stringify(results, null, 2)
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
};
|
|
407
|
+
}
|
|
402
408
|
);
|
|
403
|
-
return server;
|
|
404
409
|
}
|
|
405
410
|
|
|
406
|
-
// src/
|
|
407
|
-
|
|
411
|
+
// src/server.ts
|
|
412
|
+
function createServer(manifest, graph) {
|
|
413
|
+
const server = new McpServer3({
|
|
414
|
+
name: "contextkit",
|
|
415
|
+
version: "0.2.0"
|
|
416
|
+
});
|
|
417
|
+
registerManifestResource(server, manifest);
|
|
418
|
+
registerModelResource(server, manifest);
|
|
419
|
+
registerGlossaryResource(server, manifest);
|
|
420
|
+
registerTierResource(server, manifest);
|
|
421
|
+
registerSearchTool(server, manifest);
|
|
422
|
+
registerExplainTool(server, manifest);
|
|
423
|
+
registerValidateTool(server, graph);
|
|
424
|
+
registerTierTool(server, graph);
|
|
425
|
+
registerGoldenQueryTool(server, manifest);
|
|
426
|
+
registerGuardrailsTool(server, manifest);
|
|
427
|
+
return server;
|
|
428
|
+
}
|
|
429
|
+
async function startServer(options) {
|
|
430
|
+
const rootDir = options?.rootDir ?? process.cwd();
|
|
431
|
+
const config = loadConfig(rootDir);
|
|
432
|
+
const contextDir = options?.contextDir ?? config.context_dir;
|
|
433
|
+
const { graph } = await compile({ contextDir, config });
|
|
434
|
+
const manifest = emitManifest(graph, config);
|
|
435
|
+
const server = createServer(manifest, graph);
|
|
436
|
+
const transport = new StdioServerTransport();
|
|
437
|
+
await server.connect(transport);
|
|
438
|
+
return server;
|
|
439
|
+
}
|
|
408
440
|
export {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
441
|
+
buildModelView,
|
|
442
|
+
computeModelTier,
|
|
443
|
+
createServer,
|
|
444
|
+
explainModel,
|
|
445
|
+
findGoldenQueries,
|
|
446
|
+
findGuardrails,
|
|
447
|
+
registerGlossaryResource,
|
|
448
|
+
registerManifestResource,
|
|
449
|
+
registerModelResource,
|
|
450
|
+
registerTierResource,
|
|
451
|
+
searchManifest,
|
|
452
|
+
startServer,
|
|
453
|
+
validateGraph
|
|
422
454
|
};
|
|
423
455
|
//# sourceMappingURL=index.mjs.map
|