kibi-mcp 0.19.0 → 0.19.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/bin/kibi-mcp +1 -1
- package/dist/runtime/mcp-runtime.js +43 -0
- package/dist/server/docs.js +4 -4
- package/dist/server/json-schema-to-zod.js +73 -2
- package/dist/server/session.js +33 -15
- package/dist/server/tool-registration.js +35 -62
- package/dist/server/tools-runtime.js +52 -2
- package/dist/server/tools.js +19 -5
- package/dist/server/transport.js +9 -5
- package/dist/tools/autopilot-generate.js +37 -646
- package/dist/tools/autopilot-test-compat.js +88 -0
- package/dist/tools/autopilot-test-parsers.js +120 -0
- package/dist/tools/check.js +13 -106
- package/dist/tools/coverage.js +11 -25
- package/dist/tools/delete.js +5 -112
- package/dist/tools/discovery-adapter.js +23 -0
- package/dist/tools/entity-query.js +1 -111
- package/dist/tools/find-gaps.js +11 -26
- package/dist/tools/graph.js +11 -24
- package/dist/tools/model-requirement.js +8 -347
- package/dist/tools/mutation-context.js +28 -0
- package/dist/tools/query.js +5 -50
- package/dist/tools/relationship-validation.js +9 -116
- package/dist/tools/search.js +4 -37
- package/dist/tools/semantic-advisor.js +2 -40
- package/dist/tools/skills.js +17 -57
- package/dist/tools/sparql.js +12 -93
- package/dist/tools/status.js +5 -19
- package/dist/tools/suggest-predicates.js +11 -2448
- package/dist/tools/upsert.js +21 -816
- package/dist/tools/validate-upsert.js +12 -48
- package/dist/tools-config.js +75 -802
- package/package.json +4 -3
- package/dist/semantic-advisor/analyze-prose.js +0 -1392
- package/dist/semantic-advisor/prose-coverage-evaluator.js +0 -66
- package/dist/semantic-advisor/types.js +0 -1
package/bin/kibi-mcp
CHANGED
|
@@ -98,7 +98,7 @@ if (args.includes("-h") || args.includes("--help")) {
|
|
|
98
98
|
|
|
99
99
|
if (args.includes("--diagnostic-mode")) {
|
|
100
100
|
globalThis.__KIBI_MCP_DIAGNOSTIC_MODE = true;
|
|
101
|
-
process.env.KIBI_WORKSPACE
|
|
101
|
+
process.env.KIBI_WORKSPACE ??= process.cwd();
|
|
102
102
|
process.env.KIBI_MCP_DIAGNOSTIC_MODE = "1";
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// implements REQ-kibi-operation-interface-parity
|
|
2
|
+
export function createMcpRuntime(session) {
|
|
3
|
+
const sessionPrologs = new WeakMap();
|
|
4
|
+
return {
|
|
5
|
+
open: async (spec, options = {}) => {
|
|
6
|
+
const fs = options.fs ?? session.fs;
|
|
7
|
+
const git = options.git ?? session.git;
|
|
8
|
+
const net = options.net ?? session.net;
|
|
9
|
+
const context = {
|
|
10
|
+
workspaceRoot: options.workspaceRoot ?? session.workspaceRoot,
|
|
11
|
+
signal: options.signal ?? session.signal ?? new AbortController().signal,
|
|
12
|
+
clock: options.clock ?? session.clock ?? (() => new Date()),
|
|
13
|
+
...(fs ? { fs } : {}),
|
|
14
|
+
...(git ? { git } : {}),
|
|
15
|
+
...(net ? { net } : {}),
|
|
16
|
+
};
|
|
17
|
+
if (!spec.requiresProlog) {
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
if (options.prolog) {
|
|
21
|
+
return { ...context, prolog: options.prolog };
|
|
22
|
+
}
|
|
23
|
+
const sessionProlog = await session.ensureProlog();
|
|
24
|
+
const operationContext = {
|
|
25
|
+
...context,
|
|
26
|
+
prolog: session.adaptProlog(sessionProlog),
|
|
27
|
+
};
|
|
28
|
+
sessionPrologs.set(operationContext, sessionProlog);
|
|
29
|
+
return operationContext;
|
|
30
|
+
},
|
|
31
|
+
afterSuccess: async (spec) => {
|
|
32
|
+
if (spec.effects.includes("kb-write") &&
|
|
33
|
+
(await session.attachedBranchKbPath()) !== null) {
|
|
34
|
+
await session.refreshAttachedBranchStamp();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
close: async (context, outcome) => {
|
|
38
|
+
await session.requestCleanup?.(context, outcome);
|
|
39
|
+
sessionPrologs.delete(context);
|
|
40
|
+
},
|
|
41
|
+
sessionProlog: (context) => sessionPrologs.get(context),
|
|
42
|
+
};
|
|
43
|
+
}
|
package/dist/server/docs.js
CHANGED
|
@@ -16,9 +16,6 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
import { TOOLS } from "../tools-config.js";
|
|
19
|
-
// INTENTIONAL: TOOLS is imported as a Zod-inferred schema type; ToolConfig is the runtime
|
|
20
|
-
// interface with looser Record<string, unknown> inputSchema. The cast is safe because the
|
|
21
|
-
// tool definitions are statically authored and validated at startup.
|
|
22
19
|
const ACTIVE_TOOLS = TOOLS;
|
|
23
20
|
function renderToolsDoc() {
|
|
24
21
|
const lines = [
|
|
@@ -87,7 +84,10 @@ export const PROMPTS = [
|
|
|
87
84
|
"## Rules",
|
|
88
85
|
"- Never apply bootstrap writes without user-facing preview and explicit approval.",
|
|
89
86
|
"- `kb_autopilot_generate` is strictly read-only; synthesis is the backend, not the actor.",
|
|
90
|
-
"-
|
|
87
|
+
"- Prefer MCP tools; CLI routes are available when MCP is unavailable.",
|
|
88
|
+
"- If Kibi MCP tools are visible, use MCP. If MCP availability is unknown and a trusted local Kibi CLI is available, use its dedicated JSON routes with `--input <file|->`.",
|
|
89
|
+
"- If neither interface is available, Kibi operation is blocked; tell the operator to enable MCP or the trusted project-local CLI. Do not infer MCP availability from config file existence.",
|
|
90
|
+
"- Do not read or edit `.kb/` files directly. Query before mutate. Run `kb_upsert` sequentially. Run `kb_check` before completion.",
|
|
91
91
|
].join("\n"),
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
function hasRequiredProperties(value, schema) {
|
|
3
|
+
if (schema === null || typeof schema !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
const condition = schema;
|
|
6
|
+
const required = Array.isArray(condition.required)
|
|
7
|
+
? condition.required.filter((key) => typeof key === "string" && key.length > 0)
|
|
8
|
+
: [];
|
|
9
|
+
if (required.length > 0 &&
|
|
10
|
+
!required.every((key) => Object.hasOwn(value, key))) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(condition.anyOf) &&
|
|
14
|
+
!condition.anyOf.some((entry) => hasRequiredProperties(value, entry))) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(condition.allOf) &&
|
|
18
|
+
!condition.allOf.every((entry) => hasRequiredProperties(value, entry))) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function conditionalRequiredKeys(value, condition) {
|
|
24
|
+
if (condition === null || typeof condition !== "object")
|
|
25
|
+
return [];
|
|
26
|
+
const rule = condition;
|
|
27
|
+
if (!("if" in rule) || !("then" in rule))
|
|
28
|
+
return [];
|
|
29
|
+
if (!hasRequiredProperties(value, rule.if))
|
|
30
|
+
return [];
|
|
31
|
+
if (rule.then === null || typeof rule.then !== "object")
|
|
32
|
+
return [];
|
|
33
|
+
const required = rule.then.required;
|
|
34
|
+
return Array.isArray(required)
|
|
35
|
+
? required.filter((key) => typeof key === "string" && key.length > 0)
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
2
38
|
// implements REQ-002
|
|
3
39
|
export function jsonSchemaToZod(schema) {
|
|
4
40
|
if (!schema || typeof schema !== "object") {
|
|
@@ -43,7 +79,29 @@ export function jsonSchemaToZod(schema) {
|
|
|
43
79
|
? z.object(shape)
|
|
44
80
|
: z.looseObject(shape);
|
|
45
81
|
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
46
|
-
|
|
82
|
+
let result = description
|
|
83
|
+
? objectSchema.describe(description)
|
|
84
|
+
: objectSchema;
|
|
85
|
+
if (Array.isArray(obj.allOf)) {
|
|
86
|
+
result = result
|
|
87
|
+
.superRefine((value, context) => {
|
|
88
|
+
if (value === null || typeof value !== "object")
|
|
89
|
+
return;
|
|
90
|
+
for (const condition of obj.allOf) {
|
|
91
|
+
for (const key of conditionalRequiredKeys(value, condition)) {
|
|
92
|
+
if (!Object.hasOwn(value, key)) {
|
|
93
|
+
context.addIssue({
|
|
94
|
+
code: "custom",
|
|
95
|
+
path: [key],
|
|
96
|
+
message: `Required by conditional JSON Schema rule: ${key}`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
.meta({ allOf: obj.allOf });
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
47
105
|
}
|
|
48
106
|
case "array": {
|
|
49
107
|
const itemSchema = jsonSchemaToZod(obj.items);
|
|
@@ -55,7 +113,17 @@ export function jsonSchemaToZod(schema) {
|
|
|
55
113
|
if (typeof obj.maxItems === "number") {
|
|
56
114
|
arraySchema = arraySchema.max(obj.maxItems);
|
|
57
115
|
}
|
|
58
|
-
|
|
116
|
+
let result = description
|
|
117
|
+
? arraySchema.describe(description)
|
|
118
|
+
: arraySchema;
|
|
119
|
+
if (obj.uniqueItems === true) {
|
|
120
|
+
result = result
|
|
121
|
+
.refine((values) => Array.isArray(values) &&
|
|
122
|
+
new Set(values.map((value) => JSON.stringify(value))).size ===
|
|
123
|
+
values.length, { message: "Array items must be unique" })
|
|
124
|
+
.meta({ uniqueItems: true });
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
59
127
|
}
|
|
60
128
|
case "string": {
|
|
61
129
|
let s = z.string();
|
|
@@ -66,6 +134,9 @@ export function jsonSchemaToZod(schema) {
|
|
|
66
134
|
if (typeof obj.maxLength === "number") {
|
|
67
135
|
s = s.max(obj.maxLength);
|
|
68
136
|
}
|
|
137
|
+
if (typeof obj.pattern === "string") {
|
|
138
|
+
s = s.regex(new RegExp(obj.pattern));
|
|
139
|
+
}
|
|
69
140
|
return description ? s.describe(description) : s;
|
|
70
141
|
}
|
|
71
142
|
case "number": {
|
package/dist/server/session.js
CHANGED
|
@@ -42,6 +42,8 @@ let ensurePrologTail = Promise.resolve();
|
|
|
42
42
|
let prologResetGeneration = 0;
|
|
43
43
|
export let attachedBranchKbPath = null;
|
|
44
44
|
let attachedBranchStamp = null;
|
|
45
|
+
// implements REQ-mcp-kb-freshness
|
|
46
|
+
// covered_by TEST-mcp-kb-freshness
|
|
45
47
|
export function updateAttachedBranchStamp(stamp) {
|
|
46
48
|
attachedBranchStamp = stamp;
|
|
47
49
|
}
|
|
@@ -85,7 +87,7 @@ export function ensureBranchKbExists(workspaceRoot, branch) {
|
|
|
85
87
|
}
|
|
86
88
|
const branchPath = sessionDeps.resolveKbPath(workspaceRoot, branch);
|
|
87
89
|
if (sessionDeps.fs.existsSync(branchPath)) {
|
|
88
|
-
return;
|
|
90
|
+
return false;
|
|
89
91
|
}
|
|
90
92
|
// Try to copy from the previously active branch if available
|
|
91
93
|
const previousBranch = activeBranchName;
|
|
@@ -96,11 +98,12 @@ export function ensureBranchKbExists(workspaceRoot, branch) {
|
|
|
96
98
|
// Copy from previous branch for continuity
|
|
97
99
|
sessionDeps.copyCleanSnapshot(previousBranchPath, branchPath);
|
|
98
100
|
debugLog(`[KIBI-MCP] Created branch KB for '${branch}' from '${previousBranch}'`);
|
|
99
|
-
return;
|
|
101
|
+
return false;
|
|
100
102
|
}
|
|
101
103
|
// No previous branch available - create empty KB
|
|
102
104
|
sessionDeps.fs.mkdirSync(branchPath, { recursive: true });
|
|
103
105
|
debugLog(`[KIBI-MCP] Created empty branch KB for '${branch}'`);
|
|
106
|
+
return true;
|
|
104
107
|
}
|
|
105
108
|
export async function initiateGracefulShutdown(exitCode = 0) {
|
|
106
109
|
if (isShuttingDown) {
|
|
@@ -108,6 +111,18 @@ export async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
108
111
|
}
|
|
109
112
|
isShuttingDown = true;
|
|
110
113
|
debugLog(`[KIBI-MCP] Initiating graceful shutdown (exit code: ${exitCode})`);
|
|
114
|
+
const currentProlog = prologProcess;
|
|
115
|
+
prologProcess = null;
|
|
116
|
+
if (currentProlog?.isRunning()) {
|
|
117
|
+
debugLog("[KIBI-MCP] Cancelling active Prolog work...");
|
|
118
|
+
try {
|
|
119
|
+
await currentProlog.terminate();
|
|
120
|
+
debugLog("[KIBI-MCP] Prolog process terminated");
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error("[KIBI-MCP] Error terminating Prolog:", error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
111
126
|
// Wait for in-flight requests
|
|
112
127
|
if (inFlightRequests.size > 0) {
|
|
113
128
|
debugLog(`[KIBI-MCP] Waiting for ${inFlightRequests.size} in-flight requests to complete...`);
|
|
@@ -133,17 +148,6 @@ export async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
133
148
|
}
|
|
134
149
|
}
|
|
135
150
|
}
|
|
136
|
-
// Cleanup Prolog process
|
|
137
|
-
if (prologProcess?.isRunning()) {
|
|
138
|
-
debugLog("[KIBI-MCP] Terminating Prolog process...");
|
|
139
|
-
try {
|
|
140
|
-
await prologProcess.terminate();
|
|
141
|
-
debugLog("[KIBI-MCP] Prolog process terminated");
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
console.error("[KIBI-MCP] Error terminating Prolog:", error);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
151
|
// Exit
|
|
148
152
|
process.exit(exitCode);
|
|
149
153
|
}
|
|
@@ -271,13 +275,20 @@ async function ensurePrologUnsafe() {
|
|
|
271
275
|
// Continue anyway - we'll try to attach to the new KB
|
|
272
276
|
}
|
|
273
277
|
// Ensure new branch KB exists
|
|
274
|
-
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
278
|
+
const createdEmptyBranch = ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
275
279
|
// Attach to new branch KB
|
|
276
280
|
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
277
281
|
await assertGeneration();
|
|
278
282
|
if (!attachResult.success) {
|
|
279
283
|
throw new Error(`Failed to attach to new branch KB: ${attachResult.error || "Unknown error"}`);
|
|
280
284
|
}
|
|
285
|
+
if (createdEmptyBranch) {
|
|
286
|
+
const initialSaveResult = await prologProcess.query("kb_save");
|
|
287
|
+
await assertGeneration();
|
|
288
|
+
if (!initialSaveResult.success) {
|
|
289
|
+
throw new Error(`Failed to initialize empty branch KB: ${initialSaveResult.error || "Unknown error"}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
281
292
|
activeBranchName = targetBranch;
|
|
282
293
|
attachedBranchKbPath = kbPath;
|
|
283
294
|
attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
@@ -327,13 +338,20 @@ async function ensurePrologUnsafe() {
|
|
|
327
338
|
debugLog(`[KIBI-MCP] KIBI_BRANCH env: ${envBranch || "not set"}`);
|
|
328
339
|
debugLog(`[KIBI-MCP] Resolved branch: ${targetBranch}`);
|
|
329
340
|
activeBranchName = targetBranch;
|
|
330
|
-
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
341
|
+
const createdEmptyBranch = ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
331
342
|
const kbPath = sessionDeps.resolveKbPath(workspaceRoot, targetBranch);
|
|
332
343
|
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
333
344
|
await assertGeneration();
|
|
334
345
|
if (!attachResult.success) {
|
|
335
346
|
throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
|
|
336
347
|
}
|
|
348
|
+
if (createdEmptyBranch) {
|
|
349
|
+
const initialSaveResult = await prologProcess.query("kb_save");
|
|
350
|
+
await assertGeneration();
|
|
351
|
+
if (!initialSaveResult.success) {
|
|
352
|
+
throw new Error(`Failed to initialize empty branch KB: ${initialSaveResult.error || "Unknown error"}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
337
355
|
attachedBranchKbPath = kbPath;
|
|
338
356
|
attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
339
357
|
isInitialized = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSpec } from "kibi-cli/operations";
|
|
1
2
|
// implements REQ-002, REQ-013
|
|
2
3
|
export function registerConfiguredTools(server, runtime, registerTool) {
|
|
3
4
|
const toolDef = (name) => {
|
|
@@ -6,9 +7,23 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
6
7
|
throw new Error(`Unknown tool: ${name}`);
|
|
7
8
|
return t;
|
|
8
9
|
};
|
|
9
|
-
const
|
|
10
|
+
const prologFor = (context) => {
|
|
11
|
+
const prolog = runtime.operationRuntime.sessionProlog(context);
|
|
12
|
+
if (!prolog) {
|
|
13
|
+
throw new Error("Operation requires a session Prolog process");
|
|
14
|
+
}
|
|
15
|
+
return prolog;
|
|
16
|
+
};
|
|
17
|
+
const register = ({ name, execute }) => {
|
|
10
18
|
const definition = toolDef(name);
|
|
11
|
-
|
|
19
|
+
const publicSpec = getSpec(name);
|
|
20
|
+
const spec = {
|
|
21
|
+
name,
|
|
22
|
+
effects: publicSpec.effects,
|
|
23
|
+
requiresProlog: publicSpec.requiresProlog,
|
|
24
|
+
execute: (args, context) => execute(context, args),
|
|
25
|
+
};
|
|
26
|
+
registerTool(server, name, definition.description, definition.inputSchema, async () => undefined, runtime, spec, definition.annotations);
|
|
12
27
|
};
|
|
13
28
|
// INTENTIONAL ARGUMENT CASTS: The `args as (unknown as)? XyzArgs` casts below
|
|
14
29
|
// bridge the generic ToolHandler (which receives Record<string, unknown>) to the
|
|
@@ -16,116 +31,74 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
16
31
|
// (via jsonSchemaToZod) before the handler is invoked, so the casts are safe at runtime.
|
|
17
32
|
register({
|
|
18
33
|
name: "kb_query",
|
|
19
|
-
|
|
20
|
-
const prolog = await runtime.ensureProlog();
|
|
21
|
-
return runtime.handleKbQuery(prolog, args);
|
|
22
|
-
},
|
|
34
|
+
execute: async (context, args) => runtime.handleKbQuery(prologFor(context), args),
|
|
23
35
|
});
|
|
24
36
|
register({
|
|
25
37
|
name: "kb_search",
|
|
26
|
-
|
|
27
|
-
const prolog = await runtime.ensureProlog();
|
|
28
|
-
return runtime.handleKbSearch(prolog, args);
|
|
29
|
-
},
|
|
38
|
+
execute: async (context, args) => runtime.handleKbSearch(prologFor(context), args),
|
|
30
39
|
});
|
|
31
40
|
register({
|
|
32
41
|
name: "kb_status",
|
|
33
|
-
|
|
34
|
-
const prolog = await runtime.ensureProlog();
|
|
35
|
-
return runtime.handleKbStatus(prolog, args);
|
|
36
|
-
},
|
|
42
|
+
execute: async (context, args) => runtime.handleKbStatus(prologFor(context), args),
|
|
37
43
|
});
|
|
38
44
|
register({
|
|
39
45
|
name: "kb_skills_list",
|
|
40
|
-
|
|
46
|
+
execute: async (_context, args) => runtime.handleKbSkillsList(args),
|
|
41
47
|
});
|
|
42
48
|
register({
|
|
43
49
|
name: "kb_skills_load",
|
|
44
|
-
|
|
50
|
+
execute: async (_context, args) => runtime.handleKbSkillsLoad(args),
|
|
45
51
|
});
|
|
46
52
|
register({
|
|
47
53
|
name: "kb_skills_read",
|
|
48
|
-
|
|
54
|
+
execute: async (_context, args) => runtime.handleKbSkillsRead(args),
|
|
49
55
|
});
|
|
50
56
|
register({
|
|
51
57
|
name: "kb_find_gaps",
|
|
52
|
-
|
|
53
|
-
const prolog = await runtime.ensureProlog();
|
|
54
|
-
return runtime.handleKbFindGaps(prolog, args);
|
|
55
|
-
},
|
|
58
|
+
execute: async (context, args) => runtime.handleKbFindGaps(prologFor(context), args),
|
|
56
59
|
});
|
|
57
60
|
register({
|
|
58
61
|
name: "kb_coverage",
|
|
59
|
-
|
|
60
|
-
const prolog = await runtime.ensureProlog();
|
|
61
|
-
return runtime.handleKbCoverage(prolog, args);
|
|
62
|
-
},
|
|
62
|
+
execute: async (context, args) => runtime.handleKbCoverage(prologFor(context), args),
|
|
63
63
|
});
|
|
64
64
|
register({
|
|
65
65
|
name: "kb_graph",
|
|
66
|
-
|
|
67
|
-
const prolog = await runtime.ensureProlog();
|
|
68
|
-
return runtime.handleKbGraph(prolog, args);
|
|
69
|
-
},
|
|
66
|
+
execute: async (context, args) => runtime.handleKbGraph(prologFor(context), args),
|
|
70
67
|
});
|
|
71
68
|
register({
|
|
72
69
|
name: "kb_sparql_remote",
|
|
73
|
-
|
|
74
|
-
const prolog = await runtime.ensureProlog();
|
|
75
|
-
return runtime.handleSparql(prolog, args);
|
|
76
|
-
},
|
|
70
|
+
execute: async (context, args) => runtime.handleSparql(args, context),
|
|
77
71
|
});
|
|
78
72
|
register({
|
|
79
73
|
name: "kb_semantic_advisor",
|
|
80
|
-
|
|
74
|
+
execute: async (_context, args) => runtime.handleKbSemanticAdvisor(args),
|
|
81
75
|
});
|
|
82
76
|
register({
|
|
83
77
|
name: "kb_upsert",
|
|
84
|
-
|
|
85
|
-
const prolog = await runtime.ensureProlog();
|
|
86
|
-
return runtime.handleKbUpsert(prolog, args);
|
|
87
|
-
},
|
|
78
|
+
execute: async (context, args) => runtime.handleKbUpsert(prologFor(context), args),
|
|
88
79
|
});
|
|
89
80
|
register({
|
|
90
81
|
name: "kb_validate_upsert",
|
|
91
|
-
|
|
92
|
-
const prolog = await runtime.ensureProlog();
|
|
93
|
-
return runtime.handleKbValidateUpsert(prolog, args);
|
|
94
|
-
},
|
|
82
|
+
execute: async (context, args) => runtime.handleKbValidateUpsert(prologFor(context), args),
|
|
95
83
|
});
|
|
96
84
|
register({
|
|
97
85
|
name: "kb_delete",
|
|
98
|
-
|
|
99
|
-
const prolog = await runtime.ensureProlog();
|
|
100
|
-
return runtime.handleKbDelete(prolog, args);
|
|
101
|
-
},
|
|
86
|
+
execute: async (context, args) => runtime.handleKbDelete(prologFor(context), args),
|
|
102
87
|
});
|
|
103
88
|
register({
|
|
104
89
|
name: "kb_check",
|
|
105
|
-
|
|
106
|
-
const prolog = await runtime.ensureProlog();
|
|
107
|
-
return runtime.handleKbCheck(prolog, args);
|
|
108
|
-
},
|
|
90
|
+
execute: async (context, args) => runtime.handleKbCheck(prologFor(context), args),
|
|
109
91
|
});
|
|
110
92
|
register({
|
|
111
93
|
name: "kb_model_requirement",
|
|
112
|
-
|
|
113
|
-
const prolog = await runtime.ensureProlog();
|
|
114
|
-
return runtime.handleKbModelRequirement(prolog, args);
|
|
115
|
-
},
|
|
94
|
+
execute: async (context, args) => runtime.handleKbModelRequirement(prologFor(context), args),
|
|
116
95
|
});
|
|
117
96
|
register({
|
|
118
97
|
name: "kb_suggest_predicates",
|
|
119
|
-
|
|
120
|
-
const prolog = await runtime.ensureProlog();
|
|
121
|
-
return runtime.handleKbSuggestPredicates(prolog, args);
|
|
122
|
-
},
|
|
98
|
+
execute: async (context, args) => runtime.handleKbSuggestPredicates(prologFor(context), args),
|
|
123
99
|
});
|
|
124
100
|
register({
|
|
125
101
|
name: "kb_autopilot_generate",
|
|
126
|
-
|
|
127
|
-
const prolog = await runtime.ensureProlog();
|
|
128
|
-
return runtime.handleKbAutopilotGenerate(prolog, args);
|
|
129
|
-
},
|
|
102
|
+
execute: async (context, args) => runtime.handleKbAutopilotGenerate(args, context),
|
|
130
103
|
});
|
|
131
104
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { getSpec } from "kibi-cli/operations";
|
|
1
2
|
import { DIAGNOSTIC_MODE_ENABLED, appendUsageLogLine, classifyDiagnosticError, deriveDiagnosticFields, extractToolCallPayload, } from "../diagnostics.js";
|
|
3
|
+
import { isMcpDebugEnabled } from "../env.js";
|
|
4
|
+
import { createMcpRuntime } from "../runtime/mcp-runtime.js";
|
|
2
5
|
import { TOOLS } from "../tools-config.js";
|
|
3
6
|
import { handleKbAutopilotGenerate } from "../tools/autopilot-generate.js";
|
|
4
7
|
import { handleKbCheck } from "../tools/check.js";
|
|
@@ -11,15 +14,17 @@ import { handleKbQuery } from "../tools/query.js";
|
|
|
11
14
|
import { handleKbSearch } from "../tools/search.js";
|
|
12
15
|
import { handleKbSemanticAdvisor } from "../tools/semantic-advisor.js";
|
|
13
16
|
import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
|
|
14
|
-
import { handleSparql } from "../tools/sparql.js";
|
|
15
17
|
import { handleKbStatus } from "../tools/status.js";
|
|
16
18
|
import { handleKbSuggestPredicates } from "../tools/suggest-predicates.js";
|
|
17
19
|
import { handleKbUpsert } from "../tools/upsert.js";
|
|
18
20
|
import { handleKbValidateUpsert } from "../tools/validate-upsert.js";
|
|
21
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
22
|
+
import { readBranchKbStamp } from "./kb-freshness.js";
|
|
19
23
|
const defaultToolsServerDeps = {
|
|
20
24
|
getSessionModule: () => import("./session.js"),
|
|
21
25
|
};
|
|
22
26
|
let sessionModulePromise = null;
|
|
27
|
+
const prologPorts = new WeakMap();
|
|
23
28
|
// implements REQ-008
|
|
24
29
|
export function _setToolsServerDepsForTests(deps, resetPromise = false) {
|
|
25
30
|
defaultToolsServerDeps.getSessionModule =
|
|
@@ -39,6 +44,50 @@ async function getSessionModule() {
|
|
|
39
44
|
sessionModulePromise ??= defaultToolsServerDeps.getSessionModule();
|
|
40
45
|
return sessionModulePromise;
|
|
41
46
|
}
|
|
47
|
+
function adaptProlog(prolog) {
|
|
48
|
+
const existing = prologPorts.get(prolog);
|
|
49
|
+
if (existing) {
|
|
50
|
+
return existing;
|
|
51
|
+
}
|
|
52
|
+
let lastResult = null;
|
|
53
|
+
const port = {
|
|
54
|
+
query: async (goal) => {
|
|
55
|
+
lastResult = await prolog.query(goal);
|
|
56
|
+
return lastResult;
|
|
57
|
+
},
|
|
58
|
+
nextSolution: async () => {
|
|
59
|
+
const result = lastResult;
|
|
60
|
+
lastResult = null;
|
|
61
|
+
return result;
|
|
62
|
+
},
|
|
63
|
+
save: () => prolog.query("kb_save"),
|
|
64
|
+
};
|
|
65
|
+
prologPorts.set(prolog, port);
|
|
66
|
+
return port;
|
|
67
|
+
}
|
|
68
|
+
const operationRuntime = createMcpRuntime({
|
|
69
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
70
|
+
activeBranchName: async () => (await getSessionModule()).activeBranchName,
|
|
71
|
+
attachedBranchKbPath: async () => (await getSessionModule()).attachedBranchKbPath,
|
|
72
|
+
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
73
|
+
adaptProlog,
|
|
74
|
+
net: { fetch: (input, init) => globalThis.fetch(input, init) },
|
|
75
|
+
refreshAttachedBranchStamp: async () => {
|
|
76
|
+
const session = await getSessionModule();
|
|
77
|
+
const kbPath = session.attachedBranchKbPath;
|
|
78
|
+
if (kbPath) {
|
|
79
|
+
try {
|
|
80
|
+
session.updateAttachedBranchStamp(await readBranchKbStamp(kbPath));
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
+
if (isMcpDebugEnabled()) {
|
|
85
|
+
console.warn("[KIBI-MCP] Attached branch stamp refresh failed:", message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
42
91
|
export const DEFAULT_TOOLS_RUNTIME = {
|
|
43
92
|
diagnosticModeEnabled: () => DIAGNOSTIC_MODE_ENABLED,
|
|
44
93
|
appendUsageLogLine,
|
|
@@ -55,12 +104,13 @@ export const DEFAULT_TOOLS_RUNTIME = {
|
|
|
55
104
|
inFlightRequests: async () => (await getSessionModule()).inFlightRequests,
|
|
56
105
|
isShuttingDown: async () => (await getSessionModule()).isShuttingDown,
|
|
57
106
|
prologProcess: async () => (await getSessionModule()).prologProcess,
|
|
107
|
+
operationRuntime,
|
|
58
108
|
handleKbCheck,
|
|
59
109
|
handleKbCoverage,
|
|
60
110
|
handleKbDelete,
|
|
61
111
|
handleKbFindGaps,
|
|
62
112
|
handleKbGraph,
|
|
63
|
-
handleSparql,
|
|
113
|
+
handleSparql: (args, context) => getSpec("kb_sparql_remote").execute(args, context),
|
|
64
114
|
handleKbQuery,
|
|
65
115
|
handleKbSearch,
|
|
66
116
|
handleKbStatus,
|
package/dist/server/tools.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
import process from "node:process";
|
|
19
|
+
import { executeOperation, } from "kibi-cli/operations/runtime-types";
|
|
19
20
|
import { isMcpDebugEnabled } from "../env.js";
|
|
20
21
|
import { appendDiagnosticErrorUsage, appendDiagnosticSuccessUsage, } from "./diagnostic-usage.js";
|
|
21
22
|
import { jsonSchemaToZod } from "./json-schema-to-zod.js";
|
|
@@ -73,7 +74,7 @@ export function addTool(server, name, description, inputSchema, handler,
|
|
|
73
74
|
// INTENTIONAL: DEFAULT_TOOLS_RUNTIME is typed as ToolsRuntime<PrologProcess>; the
|
|
74
75
|
// generic TProlog parameter exists so tests can inject a mock type. The cast is safe
|
|
75
76
|
// because the runtime object satisfies the full ToolsRuntime contract at runtime.
|
|
76
|
-
runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
77
|
+
runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
|
|
77
78
|
const wrappedHandler = async (args) => {
|
|
78
79
|
const startedAt = new Date();
|
|
79
80
|
const diagnosticModeEnabled = runtime.diagnosticModeEnabled();
|
|
@@ -100,21 +101,30 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
100
101
|
}
|
|
101
102
|
// Track the handler promise in inFlightRequests Map
|
|
102
103
|
const trackedRequests = await runtime.inFlightRequests();
|
|
103
|
-
const
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const operationSpec = spec ?? {
|
|
106
|
+
name,
|
|
107
|
+
effects: ["local-read"],
|
|
108
|
+
requiresProlog: false,
|
|
109
|
+
execute: async (input, _context) => handler(input),
|
|
110
|
+
};
|
|
111
|
+
const handlerPromise = executeOperation(runtime.operationRuntime, operationSpec, businessArgs, { signal: controller.signal });
|
|
104
112
|
trackedRequests.set(requestId, handlerPromise);
|
|
105
113
|
let resetAttempted = false;
|
|
106
114
|
let resetSucceeded = false;
|
|
107
115
|
let resetError = null;
|
|
108
116
|
try {
|
|
109
117
|
// Execute handler
|
|
110
|
-
const result = await withToolTimeout(name, handlerPromise, async () => {
|
|
118
|
+
const result = await withToolTimeout(name, handlerPromise, async (error) => {
|
|
111
119
|
resetAttempted = true;
|
|
120
|
+
controller.abort(error);
|
|
112
121
|
try {
|
|
113
122
|
await runtime.resetProlog(`tool timeout: ${name}`);
|
|
114
123
|
resetSucceeded = true;
|
|
115
124
|
}
|
|
116
125
|
catch (error) {
|
|
117
|
-
resetError =
|
|
126
|
+
resetError =
|
|
127
|
+
error instanceof Error ? error.message : String(error);
|
|
118
128
|
}
|
|
119
129
|
});
|
|
120
130
|
// Log usage in diagnostic mode
|
|
@@ -163,7 +173,11 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
163
173
|
throw new Error(`Tool ${name} failed: ${err.message}`, { cause: err });
|
|
164
174
|
}
|
|
165
175
|
};
|
|
166
|
-
server.registerTool(name, {
|
|
176
|
+
server.registerTool(name, {
|
|
177
|
+
description,
|
|
178
|
+
inputSchema: jsonSchemaToZod(inputSchema),
|
|
179
|
+
...(annotations ? { annotations } : {}),
|
|
180
|
+
}, wrappedHandler);
|
|
167
181
|
}
|
|
168
182
|
// implements REQ-002, REQ-013
|
|
169
183
|
export function registerAllTools(server,
|
package/dist/server/transport.js
CHANGED
|
@@ -23,7 +23,7 @@ function debugLog(...args) {
|
|
|
23
23
|
console.error(...args);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export function setupTransportHandlers(
|
|
26
|
+
export function setupTransportHandlers(_server, transport) {
|
|
27
27
|
transport.onerror = (error) => {
|
|
28
28
|
// Stdio transport surfaces JSON parse / schema validation failures via onerror.
|
|
29
29
|
// Those errors should not crash the server: emit a JSON-RPC error (id omitted)
|
|
@@ -62,11 +62,15 @@ export function setupTransportHandlers(server, transport) {
|
|
|
62
62
|
debugLog("[KIBI-MCP] Transport closed");
|
|
63
63
|
initiateGracefulShutdown(0);
|
|
64
64
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
66
|
+
process.on(signal, () => {
|
|
67
|
+
debugLog(`[KIBI-MCP] Received ${signal}, initiating graceful shutdown`);
|
|
68
|
+
void initiateGracefulShutdown(0);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
69
71
|
}
|
|
72
|
+
// implements REQ-002
|
|
73
|
+
// covered_by TEST-mcp-kb-freshness
|
|
70
74
|
export async function connectTransport(server, transport) {
|
|
71
75
|
await server.connect(transport);
|
|
72
76
|
}
|