@aikdna/kdna-mcp-server 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/bin/kdna-mcp.mjs +172 -34
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -22,13 +22,18 @@ server provides a lower-level bridge for structured tool calls.
|
|
|
22
22
|
|------|---------|-------|--------|
|
|
23
23
|
| `kdna.inspect` | Inspect a `.kdna` asset | File path | Structured metadata |
|
|
24
24
|
| `kdna.verify` | Verify asset integrity state | File path | Pass/fail with reasons |
|
|
25
|
-
| `kdna.plan-load` | Return Core LoadPlan before loading | File path, optional password or entitlement state | LoadPlan JSON |
|
|
25
|
+
| `kdna.plan-load` | Return Core LoadPlan before loading | File path, optional password-presence or entitlement state | LoadPlan JSON |
|
|
26
26
|
| `kdna.load` | Load an authorized `.kdna` profile | File path, optional profile, password, or entitlement state | Runtime Capsule |
|
|
27
27
|
| `kdna.available-local` | List local `.kdna` assets | Root directory | Local asset inventory |
|
|
28
28
|
| `kdna.match` | Rank candidate assets for a task string | Task description | Scored list with fit signals |
|
|
29
29
|
|
|
30
30
|
## Install & Run
|
|
31
31
|
|
|
32
|
+
The commands below currently resolve the published MCP 0.4.1 package. They do
|
|
33
|
+
not install or prove the MCP 0.4.2 / Core 0.20.0 source checkpoint described in
|
|
34
|
+
this repository's current changelog; those candidates must remain a coordinated
|
|
35
|
+
package set and must not be mixed with the published wave.
|
|
36
|
+
|
|
32
37
|
```bash
|
|
33
38
|
npm install -g @aikdna/kdna-mcp-server
|
|
34
39
|
kdna-mcp
|
|
@@ -106,6 +111,14 @@ node bin/kdna-mcp.mjs
|
|
|
106
111
|
# Agent consumes only the returned Runtime Capsule context
|
|
107
112
|
```
|
|
108
113
|
|
|
114
|
+
In the 0.4.2 source checkpoint, the returned Capsule uses
|
|
115
|
+
`type: "kdna.runtime-capsule"` and
|
|
116
|
+
`contract_version: "0.1.0"`. The KDNA Agent Host protocol has the independent
|
|
117
|
+
coordinate `protocol_version: "0.1.0"`; it is not the MCP transport's
|
|
118
|
+
`protocolVersion`. This bridge does not execute an Agent Host and therefore
|
|
119
|
+
does not create or claim a Host receipt or Judgment Trace. A consuming Host
|
|
120
|
+
must use Core's correlated runtime-contract APIs for that boundary.
|
|
121
|
+
|
|
109
122
|
## Relationship to kdna-loader skill
|
|
110
123
|
|
|
111
124
|
The MCP server and kdna-loader skill are complementary:
|
package/bin/kdna-mcp.mjs
CHANGED
|
@@ -7,6 +7,10 @@ import { spawnSync } from 'node:child_process';
|
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
8
8
|
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
|
+
const Ajv = require('ajv');
|
|
11
|
+
const Ajv2020 = require('ajv/dist/2020');
|
|
12
|
+
const addFormats = require('ajv-formats');
|
|
13
|
+
const loadPlanSchema = require('@aikdna/kdna-core/schema/load-plan.schema.json');
|
|
10
14
|
const packageInfo = require('../package.json');
|
|
11
15
|
const {
|
|
12
16
|
detectContainerFormat,
|
|
@@ -23,6 +27,7 @@ const tools = [
|
|
|
23
27
|
description: 'Inspect a .kdna asset without extracting it.',
|
|
24
28
|
inputSchema: {
|
|
25
29
|
type: 'object',
|
|
30
|
+
additionalProperties: false,
|
|
26
31
|
required: ['assetPath'],
|
|
27
32
|
properties: { assetPath: { type: 'string' }, verify: { type: 'boolean' } },
|
|
28
33
|
},
|
|
@@ -32,6 +37,7 @@ const tools = [
|
|
|
32
37
|
description: 'Verify a .kdna asset integrity state.',
|
|
33
38
|
inputSchema: {
|
|
34
39
|
type: 'object',
|
|
40
|
+
additionalProperties: false,
|
|
35
41
|
required: ['assetPath'],
|
|
36
42
|
properties: {
|
|
37
43
|
assetPath: { type: 'string' },
|
|
@@ -46,6 +52,7 @@ const tools = [
|
|
|
46
52
|
description: 'Load a .kdna profile and return agent context.',
|
|
47
53
|
inputSchema: {
|
|
48
54
|
type: 'object',
|
|
55
|
+
additionalProperties: false,
|
|
49
56
|
required: ['assetPath'],
|
|
50
57
|
properties: {
|
|
51
58
|
assetPath: { type: 'string' },
|
|
@@ -61,6 +68,7 @@ const tools = [
|
|
|
61
68
|
description: 'Return the Core LoadPlan for a .kdna asset before loading.',
|
|
62
69
|
inputSchema: {
|
|
63
70
|
type: 'object',
|
|
71
|
+
additionalProperties: false,
|
|
64
72
|
required: ['assetPath'],
|
|
65
73
|
properties: {
|
|
66
74
|
assetPath: { type: 'string' },
|
|
@@ -74,9 +82,10 @@ const tools = [
|
|
|
74
82
|
description: 'List local .kdna files without using a registry.',
|
|
75
83
|
inputSchema: {
|
|
76
84
|
type: 'object',
|
|
85
|
+
additionalProperties: false,
|
|
77
86
|
properties: {
|
|
78
87
|
root: { type: 'string' },
|
|
79
|
-
maxDepth: { type: '
|
|
88
|
+
maxDepth: { type: 'integer', minimum: 0 },
|
|
80
89
|
},
|
|
81
90
|
},
|
|
82
91
|
},
|
|
@@ -85,6 +94,7 @@ const tools = [
|
|
|
85
94
|
description: 'Rank .kdna assets for a task string.',
|
|
86
95
|
inputSchema: {
|
|
87
96
|
type: 'object',
|
|
97
|
+
additionalProperties: false,
|
|
88
98
|
required: ['input', 'assetPaths'],
|
|
89
99
|
properties: {
|
|
90
100
|
input: { type: 'string' },
|
|
@@ -94,11 +104,52 @@ const tools = [
|
|
|
94
104
|
},
|
|
95
105
|
];
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
108
|
+
const toolValidators = new Map(tools.map((tool) => [tool.name, ajv.compile(tool.inputSchema)]));
|
|
109
|
+
const contractAjv = new Ajv2020({ allErrors: true, strict: false });
|
|
110
|
+
addFormats(contractAjv);
|
|
111
|
+
const validateLoadPlan = contractAjv.compile(loadPlanSchema);
|
|
112
|
+
|
|
113
|
+
class JsonRpcError extends Error {
|
|
114
|
+
constructor(code, message) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.code = code;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validatedJsonRpcMessage(message) {
|
|
121
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) {
|
|
122
|
+
throw new JsonRpcError(-32600, 'Invalid Request');
|
|
123
|
+
}
|
|
124
|
+
if (message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
|
|
125
|
+
throw new JsonRpcError(-32600, 'Invalid Request');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const hasId = Object.prototype.hasOwnProperty.call(message, 'id');
|
|
129
|
+
if (hasId) {
|
|
130
|
+
const validId =
|
|
131
|
+
typeof message.id === 'string' ||
|
|
132
|
+
(typeof message.id === 'number' && Number.isFinite(message.id));
|
|
133
|
+
if (!validId) throw new JsonRpcError(-32600, 'Invalid Request');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
id: message.id,
|
|
138
|
+
method: message.method,
|
|
139
|
+
params: message.params === undefined ? {} : message.params,
|
|
140
|
+
paramsValid:
|
|
141
|
+
message.params === undefined ||
|
|
142
|
+
(message.params !== null && typeof message.params === 'object' && !Array.isArray(message.params)),
|
|
143
|
+
isNotification: !hasId,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sendResult(id, result) {
|
|
148
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function sendError(id, code, message) {
|
|
152
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } })}\n`);
|
|
102
153
|
}
|
|
103
154
|
|
|
104
155
|
function textResult(value) {
|
|
@@ -107,6 +158,26 @@ function textResult(value) {
|
|
|
107
158
|
};
|
|
108
159
|
}
|
|
109
160
|
|
|
161
|
+
function toolErrorResult(error) {
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: 'text', text: error?.message || String(error) }],
|
|
164
|
+
isError: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function validatedToolCall(params) {
|
|
169
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
170
|
+
throw new JsonRpcError(-32602, 'Invalid params');
|
|
171
|
+
}
|
|
172
|
+
if (typeof params.name !== 'string' || !toolValidators.has(params.name)) {
|
|
173
|
+
throw new JsonRpcError(-32602, 'Invalid params');
|
|
174
|
+
}
|
|
175
|
+
const args = params.arguments === undefined ? {} : params.arguments;
|
|
176
|
+
const validateInput = toolValidators.get(params.name);
|
|
177
|
+
if (!validateInput(args)) throw new JsonRpcError(-32602, 'Invalid params');
|
|
178
|
+
return { name: params.name, args };
|
|
179
|
+
}
|
|
180
|
+
|
|
110
181
|
function isKdnaAsset(assetPath) {
|
|
111
182
|
if (!assetPath) return false;
|
|
112
183
|
try {
|
|
@@ -139,20 +210,35 @@ function findLocalAssets(root = defaultAssetRoot(), maxDepth = 3) {
|
|
|
139
210
|
const full = path.join(dir, entry.name);
|
|
140
211
|
if (entry.isDirectory()) {
|
|
141
212
|
visit(full, depth + 1);
|
|
142
|
-
} else if (entry.isFile() && entry.name.endsWith('.kdna')
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
213
|
+
} else if (entry.isFile() && entry.name.endsWith('.kdna')) {
|
|
214
|
+
try {
|
|
215
|
+
if (detectContainerFormat(full) !== 'kdna') continue;
|
|
216
|
+
const inspection = inspect(full);
|
|
217
|
+
const validation = validate(full);
|
|
218
|
+
found.push({
|
|
219
|
+
path: full,
|
|
220
|
+
kind: 'kdna_asset',
|
|
221
|
+
asset_id: inspection.asset_id,
|
|
222
|
+
title: inspection.title,
|
|
223
|
+
version: inspection.version,
|
|
224
|
+
judgment_version: inspection.judgment_version,
|
|
225
|
+
checksums_present: Boolean(inspection.checksums_present),
|
|
226
|
+
loadable: Boolean(validation.overall_valid),
|
|
227
|
+
problems: validation.overall_valid ? [] : validation.problems || [],
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
found.push({
|
|
231
|
+
path: full,
|
|
232
|
+
kind: 'kdna_asset',
|
|
233
|
+
asset_id: null,
|
|
234
|
+
title: null,
|
|
235
|
+
version: null,
|
|
236
|
+
judgment_version: null,
|
|
237
|
+
checksums_present: false,
|
|
238
|
+
loadable: false,
|
|
239
|
+
problems: ['KDNA_ASSET_INVALID'],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
156
242
|
}
|
|
157
243
|
}
|
|
158
244
|
}
|
|
@@ -174,15 +260,31 @@ function runCliPlanLoad(args = {}) {
|
|
|
174
260
|
if (result.error) {
|
|
175
261
|
throw new Error(`Core planLoad is unavailable and kdna CLI failed: ${result.error.message}`);
|
|
176
262
|
}
|
|
263
|
+
if (result.status !== 0 && result.status !== 3) {
|
|
264
|
+
throw new Error(`Core planLoad is unavailable and kdna CLI exited ${result.status}`);
|
|
265
|
+
}
|
|
266
|
+
let plan;
|
|
177
267
|
try {
|
|
178
|
-
if (result.stdout && result.stdout.trim())
|
|
268
|
+
if (result.stdout && result.stdout.trim()) plan = JSON.parse(result.stdout);
|
|
179
269
|
} catch (error) {
|
|
180
270
|
throw new Error(`Core planLoad is unavailable and kdna CLI returned non-JSON output: ${error.message}`);
|
|
181
271
|
}
|
|
182
|
-
if (
|
|
183
|
-
throw new Error(
|
|
272
|
+
if (!validateLoadPlan(plan)) {
|
|
273
|
+
throw new Error('Core planLoad is unavailable and kdna CLI returned an invalid LoadPlan');
|
|
184
274
|
}
|
|
185
|
-
|
|
275
|
+
if (
|
|
276
|
+
plan.source.kind !== 'file' ||
|
|
277
|
+
path.resolve(plan.source.path) !== path.resolve(args.assetPath)
|
|
278
|
+
) {
|
|
279
|
+
throw new Error('Core planLoad is unavailable and kdna CLI returned an uncorrelated LoadPlan');
|
|
280
|
+
}
|
|
281
|
+
if (result.status === 0 && plan.can_load_now !== true) {
|
|
282
|
+
throw new Error('Core planLoad is unavailable and kdna CLI returned a non-loadable success');
|
|
283
|
+
}
|
|
284
|
+
if (result.status === 3 && plan.can_load_now !== false) {
|
|
285
|
+
throw new Error('Core planLoad is unavailable and kdna CLI returned a loadable denial');
|
|
286
|
+
}
|
|
287
|
+
return plan;
|
|
186
288
|
}
|
|
187
289
|
|
|
188
290
|
function planLoadThroughCoreOrCli(args = {}) {
|
|
@@ -228,15 +330,14 @@ async function callTool(name, args = {}) {
|
|
|
228
330
|
return textResult(await matchDomain(args.input || '', args.assetPaths || []));
|
|
229
331
|
}
|
|
230
332
|
if (name === 'kdna.available-local') {
|
|
231
|
-
return textResult(findLocalAssets(args.root, args.maxDepth
|
|
333
|
+
return textResult(findLocalAssets(args.root, args.maxDepth ?? 3));
|
|
232
334
|
}
|
|
233
335
|
throw new Error(`Unknown tool: ${name}`);
|
|
234
336
|
}
|
|
235
337
|
|
|
236
|
-
async function handle(
|
|
237
|
-
const { id, method, params = {} } = message;
|
|
338
|
+
async function handle({ id, method, params }) {
|
|
238
339
|
if (method === 'initialize') {
|
|
239
|
-
|
|
340
|
+
sendResult(id, {
|
|
240
341
|
protocolVersion: '2024-11-05',
|
|
241
342
|
capabilities: { tools: {} },
|
|
242
343
|
serverInfo: { name: '@aikdna/kdna-mcp-server', version: packageInfo.version },
|
|
@@ -244,14 +345,28 @@ async function handle(message) {
|
|
|
244
345
|
return;
|
|
245
346
|
}
|
|
246
347
|
if (method === 'tools/list') {
|
|
247
|
-
|
|
348
|
+
sendResult(id, { tools });
|
|
248
349
|
return;
|
|
249
350
|
}
|
|
250
351
|
if (method === 'tools/call') {
|
|
251
|
-
|
|
352
|
+
let call;
|
|
353
|
+
try {
|
|
354
|
+
call = validatedToolCall(params);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error instanceof JsonRpcError) {
|
|
357
|
+
sendError(id, error.code, error.message);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
sendResult(id, await callTool(call.name, call.args));
|
|
364
|
+
} catch (error) {
|
|
365
|
+
sendResult(id, toolErrorResult(error));
|
|
366
|
+
}
|
|
252
367
|
return;
|
|
253
368
|
}
|
|
254
|
-
if (id !== undefined)
|
|
369
|
+
if (id !== undefined) sendError(id, -32601, 'Method not found');
|
|
255
370
|
}
|
|
256
371
|
|
|
257
372
|
const rl = readline.createInterface({ input: process.stdin });
|
|
@@ -260,8 +375,31 @@ rl.on('line', async (line) => {
|
|
|
260
375
|
let message;
|
|
261
376
|
try {
|
|
262
377
|
message = JSON.parse(line);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
378
|
+
} catch {
|
|
379
|
+
sendError(null, -32700, 'Parse error');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let request;
|
|
384
|
+
try {
|
|
385
|
+
request = validatedJsonRpcMessage(message);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (error instanceof JsonRpcError) {
|
|
388
|
+
sendError(null, error.code, error.message);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
sendError(null, -32603, 'Internal error');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (request.isNotification) return;
|
|
396
|
+
if (!request.paramsValid) {
|
|
397
|
+
sendError(request.id, -32602, 'Invalid params');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
await handle(request);
|
|
402
|
+
} catch {
|
|
403
|
+
sendError(request.id, -32603, 'Internal error');
|
|
266
404
|
}
|
|
267
405
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "MCP server for loading, inspecting, verifying, and matching KDNA .kdna assets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://aikdna.com",
|
|
@@ -29,12 +29,18 @@
|
|
|
29
29
|
"NOTICE"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"test": "node --test test/*.test.mjs",
|
|
33
|
-
"
|
|
32
|
+
"test": "node scripts/verify-core-candidate.mjs && node ../scripts/check-current-names.mjs && node --test test/*.test.mjs",
|
|
33
|
+
"check:core-candidate": "node scripts/verify-core-candidate.mjs",
|
|
34
|
+
"check:current-names": "node ../scripts/check-current-names.mjs",
|
|
35
|
+
"naming:check": "node ../scripts/check-current-names.mjs",
|
|
36
|
+
"release:check": "node ../scripts/release-check.js",
|
|
37
|
+
"release:dependency-guard": "node ../scripts/release-dependency-guard.js",
|
|
38
|
+
"release:preflight": "node ../scripts/release-preflight.js",
|
|
39
|
+
"prepublishOnly": "npm test && npm run release:check && npm run release:dependency-guard"
|
|
34
40
|
},
|
|
35
41
|
"license": "Apache-2.0",
|
|
36
42
|
"dependencies": {
|
|
37
|
-
"@aikdna/kdna-core": "0.
|
|
43
|
+
"@aikdna/kdna-core": "0.20.0",
|
|
38
44
|
"ajv": "^8.20.0",
|
|
39
45
|
"ajv-formats": "^3.0.1"
|
|
40
46
|
},
|