kibi-mcp 0.17.3 → 0.17.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/diagnostics-helpers.js +137 -0
- package/dist/diagnostics.js +10 -102
- package/dist/server/diagnostic-usage.js +74 -0
- package/dist/server/json-schema-to-zod.js +104 -0
- package/dist/server/session.js +1 -1
- package/dist/server/tool-registration.js +128 -0
- package/dist/server/tool-types.js +1 -0
- package/dist/server/tools-runtime.js +76 -0
- package/dist/server/tools.js +24 -287
- package/dist/tools/upsert.js +23 -8
- package/package.json +4 -7
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const SECRET_LIKE_KEY_RE = /(token|secret|password|authorization|api[_-]?key|bearer)/i;
|
|
3
|
+
const DIAGNOSTIC_PHASE_BY_STAGE = {
|
|
4
|
+
persistence: "persistence",
|
|
5
|
+
prolog_runtime: "runtime",
|
|
6
|
+
prolog_lifecycle: "runtime",
|
|
7
|
+
tool_timeout: "runtime",
|
|
8
|
+
validation: "validation",
|
|
9
|
+
handler: "handler",
|
|
10
|
+
};
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function redactSecretLikeValue(value) {
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(redactSecretLikeValue);
|
|
17
|
+
if (!isPlainObject(value))
|
|
18
|
+
return value;
|
|
19
|
+
const redacted = {};
|
|
20
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
21
|
+
redacted[key] = SECRET_LIKE_KEY_RE.test(key)
|
|
22
|
+
? "[REDACTED]"
|
|
23
|
+
: redactSecretLikeValue(nestedValue);
|
|
24
|
+
}
|
|
25
|
+
return redacted;
|
|
26
|
+
}
|
|
27
|
+
function canonicalizeJsonValue(value) {
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return value.map(canonicalizeJsonValue);
|
|
30
|
+
if (!isPlainObject(value))
|
|
31
|
+
return value;
|
|
32
|
+
return Object.fromEntries(Object.keys(value)
|
|
33
|
+
.sort()
|
|
34
|
+
.map((key) => [key, canonicalizeJsonValue(value[key])]));
|
|
35
|
+
}
|
|
36
|
+
function stableJsonStringify(value) {
|
|
37
|
+
return JSON.stringify(canonicalizeJsonValue(value));
|
|
38
|
+
}
|
|
39
|
+
export function redactDiagnosticArgs(args) {
|
|
40
|
+
return redactSecretLikeValue(args);
|
|
41
|
+
}
|
|
42
|
+
export function deriveDiagnosticRetryKey(toolName, args) {
|
|
43
|
+
const hash = createHash("sha256")
|
|
44
|
+
.update(toolName)
|
|
45
|
+
.update("\0")
|
|
46
|
+
.update(stableJsonStringify(redactDiagnosticArgs(args)))
|
|
47
|
+
.digest("hex");
|
|
48
|
+
return `retry_${hash.slice(0, 16)}`;
|
|
49
|
+
}
|
|
50
|
+
export function classifyDiagnosticError(error) {
|
|
51
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
52
|
+
const lower = err.message.toLowerCase();
|
|
53
|
+
if (lower.includes("stale_snapshot"))
|
|
54
|
+
return buildErrorFields(err, "stale_snapshot", "persistence", "KB snapshot is stale; refresh/retry after the latest KB state is attached.");
|
|
55
|
+
if (lower.includes("unknown option") && lower.includes("h for help"))
|
|
56
|
+
return buildErrorFields(err, "prolog_unknown_option", "prolog_runtime", "Prolog rejected startup/module/query options; inspect MCP package wiring and Prolog invocation.");
|
|
57
|
+
if (lower.includes("prolog process not started"))
|
|
58
|
+
return buildErrorFields(err, "prolog_process_not_started", "prolog_lifecycle", "Prolog process is unavailable; restart the MCP server before retrying.");
|
|
59
|
+
if (lower.includes("resetting prolog worker") ||
|
|
60
|
+
lower.includes("prolog worker reset"))
|
|
61
|
+
return buildErrorFields(err, "prolog_worker_reset", "prolog_lifecycle", "Prolog worker was reset so the next MCP call can start from a fresh worker.");
|
|
62
|
+
if (/timed out after \d+ms/i.test(err.message) ||
|
|
63
|
+
lower.includes("tool timeout"))
|
|
64
|
+
return buildErrorFields(err, "tool_timeout", "tool_timeout", "MCP tool execution exceeded its bounded timeout.");
|
|
65
|
+
if (lower.includes("coarsely while granular symbols are available"))
|
|
66
|
+
return buildErrorFields(err, "coarse_symbol_linkage", "validation", "Symbol traceability targeted a coarse file/module while narrower exported symbols exist.");
|
|
67
|
+
if (err.message.startsWith("Entity validation failed:"))
|
|
68
|
+
return buildErrorFields(err, "entity_validation_failed", "validation", "Entity payload failed schema validation.");
|
|
69
|
+
if (err.message.startsWith("Relationship validation failed"))
|
|
70
|
+
return buildErrorFields(err, "relationship_validation_failed", "validation", "Relationship payload failed schema validation.");
|
|
71
|
+
if (err.message.startsWith("Relationship source must match the upserted entity"))
|
|
72
|
+
return buildErrorFields(err, "relationship_source_mismatch", "validation", "Relationship source did not match the entity being upserted.");
|
|
73
|
+
if (lower.includes("module load failed"))
|
|
74
|
+
return buildErrorFields(err, "prolog_module_load_failed", "prolog_runtime", "Prolog failed to load an execution module.");
|
|
75
|
+
if (lower.includes("query failed"))
|
|
76
|
+
return buildErrorFields(err, "prolog_query_failed", "prolog_runtime", "Prolog query execution failed.");
|
|
77
|
+
return buildErrorFields(err, "handler_error", "handler", "Unhandled MCP handler error.");
|
|
78
|
+
}
|
|
79
|
+
export function deriveDiagnosticHints(input) {
|
|
80
|
+
const error = input.error instanceof Error ? input.error : new Error(String(input.error));
|
|
81
|
+
const message = error.message.toLowerCase();
|
|
82
|
+
if (message.includes("invalid value 'implemented'"))
|
|
83
|
+
return "invalid_status: use one of the schema-accepted statuses (for example open, active, accepted, passing).";
|
|
84
|
+
if (message.includes("additional properties"))
|
|
85
|
+
return "additional_properties: remove unsupported fields from the payload and retry with schema-only arguments.";
|
|
86
|
+
const { error_category, error_stage, error_summary } = classifyDiagnosticError(error);
|
|
87
|
+
const mappedStage = DIAGNOSTIC_PHASE_BY_STAGE[error_stage] ?? error_stage;
|
|
88
|
+
if (error_category === "tool_timeout")
|
|
89
|
+
return `tool_timeout ${mappedStage}: retry the same request after reducing payload size or scope.`;
|
|
90
|
+
if (error_category === "relationship_source_mismatch")
|
|
91
|
+
return `relationship_source_mismatch ${mappedStage}: align the relationship source with the upserted entity id.`;
|
|
92
|
+
if (error_category === "prolog_process_not_started")
|
|
93
|
+
return `prolog_process_not_started ${mappedStage}: restart the MCP server before retrying the tool call.`;
|
|
94
|
+
if (error_category === "prolog_query_failed")
|
|
95
|
+
return `prolog_query_failed ${mappedStage}: inspect the Prolog query or workspace state and retry.`;
|
|
96
|
+
return `${error_category} ${mappedStage}: ${error_summary}`;
|
|
97
|
+
}
|
|
98
|
+
export function buildDiagnosticToolCall(input) {
|
|
99
|
+
const diagnosticError = input.error
|
|
100
|
+
? classifyDiagnosticError(input.error)
|
|
101
|
+
: null;
|
|
102
|
+
const payload = isPlainObject(input.args)
|
|
103
|
+
? extractToolCallPayload(input.args)
|
|
104
|
+
: { businessArgs: null, telemetry: null };
|
|
105
|
+
return {
|
|
106
|
+
schema_version: 1,
|
|
107
|
+
canonical_tool: input.tool,
|
|
108
|
+
request_id: input.requestId,
|
|
109
|
+
diagnostic_phase: input.diagnosticPhase,
|
|
110
|
+
business_args: payload.businessArgs,
|
|
111
|
+
raw_args_redacted: redactDiagnosticArgs(input.args),
|
|
112
|
+
retry_key: deriveDiagnosticRetryKey(input.tool, input.args),
|
|
113
|
+
hint: deriveDiagnosticHints({
|
|
114
|
+
tool: input.tool,
|
|
115
|
+
error: input.error ?? new Error("diagnostic tool call"),
|
|
116
|
+
businessArgs: payload.businessArgs,
|
|
117
|
+
}),
|
|
118
|
+
diagnostic_error: diagnosticError,
|
|
119
|
+
diagnostic_telemetry: input.telemetry ?? payload.telemetry,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function extractToolCallPayload(args) {
|
|
123
|
+
const { _diagnostic_telemetry, ...businessArgs } = args;
|
|
124
|
+
const telemetry = _diagnostic_telemetry && typeof _diagnostic_telemetry === "object"
|
|
125
|
+
? _diagnostic_telemetry
|
|
126
|
+
: null;
|
|
127
|
+
return { businessArgs, telemetry };
|
|
128
|
+
}
|
|
129
|
+
function buildErrorFields(error, category, stage, summary) {
|
|
130
|
+
return {
|
|
131
|
+
error_name: error.name,
|
|
132
|
+
error_message: error.message,
|
|
133
|
+
error_category: category,
|
|
134
|
+
error_stage: stage,
|
|
135
|
+
error_summary: summary,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/dist/diagnostics.js
CHANGED
|
@@ -1,39 +1,19 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
-
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
-
|
|
5
|
-
This program is free software: you can redistribute it and/or modify
|
|
6
|
-
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
-
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
(at your option) any later version.
|
|
9
|
-
|
|
10
|
-
This program is distributed in the hope that it will be useful,
|
|
11
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
GNU Affero General Public License for more details.
|
|
14
|
-
|
|
15
|
-
You should have received a copy of the GNU Affero General Public License
|
|
16
|
-
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
2
|
+
* Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
* Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
17
9
|
*/
|
|
18
10
|
import fs from "node:fs";
|
|
19
11
|
import path from "node:path";
|
|
20
12
|
import { resolveWorkspaceRoot } from "./workspace.js";
|
|
13
|
+
export { buildDiagnosticToolCall, classifyDiagnosticError, deriveDiagnosticHints, deriveDiagnosticRetryKey, extractToolCallPayload, redactDiagnosticArgs, } from "./diagnostics-helpers.js";
|
|
21
14
|
const DIAGNOSTIC_MODE_FLAG = "--diagnostic-mode";
|
|
22
|
-
/**
|
|
23
|
-
* Whether diagnostic mode is enabled via CLI flag.
|
|
24
|
-
* Set during server startup and never changes at runtime.
|
|
25
|
-
*/
|
|
26
15
|
export const DIAGNOSTIC_MODE_ENABLED = process.argv.includes(DIAGNOSTIC_MODE_FLAG);
|
|
27
|
-
/**
|
|
28
|
-
* Path to the diagnostic usage log file.
|
|
29
|
-
* Only valid when DIAGNOSTIC_MODE_ENABLED is true.
|
|
30
|
-
*/
|
|
31
16
|
let diagnosticUsageLogPath = null;
|
|
32
|
-
/**
|
|
33
|
-
* Initialize diagnostic mode: set up usage.log path.
|
|
34
|
-
* Called once during server startup.
|
|
35
|
-
*/
|
|
36
|
-
// implements REQ-008
|
|
37
17
|
export function initializeDiagnosticMode(enabled = DIAGNOSTIC_MODE_ENABLED) {
|
|
38
18
|
diagnosticUsageLogPath = null;
|
|
39
19
|
if (!enabled) {
|
|
@@ -44,24 +24,14 @@ export function initializeDiagnosticMode(enabled = DIAGNOSTIC_MODE_ENABLED) {
|
|
|
44
24
|
diagnosticUsageLogPath = path.join(workspaceRoot, ".kb", "usage.log");
|
|
45
25
|
process.env.KIBI_MCP_DIAGNOSTIC_MODE = "1";
|
|
46
26
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Append a JSON line to the usage.log file.
|
|
49
|
-
* No-op if diagnostic mode is not enabled.
|
|
50
|
-
*/
|
|
51
|
-
// implements REQ-008
|
|
52
27
|
export function appendUsageLogLine(entry) {
|
|
53
|
-
if (!diagnosticUsageLogPath)
|
|
28
|
+
if (!diagnosticUsageLogPath)
|
|
54
29
|
return;
|
|
55
|
-
}
|
|
56
|
-
const logDir = path.dirname(diagnosticUsageLogPath);
|
|
57
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
30
|
+
fs.mkdirSync(path.dirname(diagnosticUsageLogPath), { recursive: true });
|
|
58
31
|
fs.appendFileSync(diagnosticUsageLogPath, `${JSON.stringify(entry)}\n`, {
|
|
59
32
|
encoding: "utf8",
|
|
60
33
|
});
|
|
61
34
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Schema for _diagnostic_telemetry field added to tool inputs in diagnostic mode.
|
|
64
|
-
*/
|
|
65
35
|
export const DIAGNOSTIC_TELEMETRY_SCHEMA = {
|
|
66
36
|
type: "object",
|
|
67
37
|
description: "REQUIRED when diagnostic mode is on. Provide self-reflection metadata about this tool call.",
|
|
@@ -88,58 +58,6 @@ export const DIAGNOSTIC_TELEMETRY_SCHEMA = {
|
|
|
88
58
|
},
|
|
89
59
|
},
|
|
90
60
|
};
|
|
91
|
-
// implements REQ-002
|
|
92
|
-
export function classifyDiagnosticError(error) {
|
|
93
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
94
|
-
const message = err.message;
|
|
95
|
-
const lower = message.toLowerCase();
|
|
96
|
-
if (lower.includes("stale_snapshot")) {
|
|
97
|
-
return buildErrorFields(err, "stale_snapshot", "persistence", "KB snapshot is stale; refresh/retry after the latest KB state is attached.");
|
|
98
|
-
}
|
|
99
|
-
if (lower.includes("unknown option") && lower.includes("h for help")) {
|
|
100
|
-
return buildErrorFields(err, "prolog_unknown_option", "prolog_runtime", "Prolog rejected startup/module/query options; inspect MCP package wiring and Prolog invocation.");
|
|
101
|
-
}
|
|
102
|
-
if (lower.includes("prolog process not started")) {
|
|
103
|
-
return buildErrorFields(err, "prolog_process_not_started", "prolog_lifecycle", "Prolog process is unavailable; restart the MCP server before retrying.");
|
|
104
|
-
}
|
|
105
|
-
if (lower.includes("resetting prolog worker") ||
|
|
106
|
-
lower.includes("prolog worker reset")) {
|
|
107
|
-
return buildErrorFields(err, "prolog_worker_reset", "prolog_lifecycle", "Prolog worker was reset so the next MCP call can start from a fresh worker.");
|
|
108
|
-
}
|
|
109
|
-
if (/timed out after \d+ms/i.test(message) ||
|
|
110
|
-
lower.includes("tool timeout")) {
|
|
111
|
-
return buildErrorFields(err, "tool_timeout", "tool_timeout", "MCP tool execution exceeded its bounded timeout.");
|
|
112
|
-
}
|
|
113
|
-
if (lower.includes("coarsely while granular symbols are available")) {
|
|
114
|
-
return buildErrorFields(err, "coarse_symbol_linkage", "validation", "Symbol traceability targeted a coarse file/module while narrower exported symbols exist.");
|
|
115
|
-
}
|
|
116
|
-
if (message.startsWith("Entity validation failed:")) {
|
|
117
|
-
return buildErrorFields(err, "entity_validation_failed", "validation", "Entity payload failed schema validation.");
|
|
118
|
-
}
|
|
119
|
-
if (message.startsWith("Relationship validation failed")) {
|
|
120
|
-
return buildErrorFields(err, "relationship_validation_failed", "validation", "Relationship payload failed schema validation.");
|
|
121
|
-
}
|
|
122
|
-
if (message.startsWith("Relationship source must match the upserted entity")) {
|
|
123
|
-
return buildErrorFields(err, "relationship_source_mismatch", "validation", "Relationship source did not match the entity being upserted.");
|
|
124
|
-
}
|
|
125
|
-
if (lower.includes("module load failed")) {
|
|
126
|
-
return buildErrorFields(err, "prolog_module_load_failed", "prolog_runtime", "Prolog failed to load an execution module.");
|
|
127
|
-
}
|
|
128
|
-
if (lower.includes("query failed")) {
|
|
129
|
-
return buildErrorFields(err, "prolog_query_failed", "prolog_runtime", "Prolog query execution failed.");
|
|
130
|
-
}
|
|
131
|
-
return buildErrorFields(err, "handler_error", "handler", "Unhandled MCP handler error.");
|
|
132
|
-
}
|
|
133
|
-
function buildErrorFields(error, category, stage, summary) {
|
|
134
|
-
return {
|
|
135
|
-
error_name: error.name,
|
|
136
|
-
error_message: error.message,
|
|
137
|
-
error_category: category,
|
|
138
|
-
error_stage: stage,
|
|
139
|
-
error_summary: summary,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
// implements REQ-002
|
|
143
61
|
export function deriveDiagnosticFields(toolName, args, telemetry, result) {
|
|
144
62
|
const fields = {
|
|
145
63
|
telemetry_status: telemetry ? "provided" : "missing",
|
|
@@ -172,13 +90,3 @@ export function deriveDiagnosticFields(toolName, args, telemetry, result) {
|
|
|
172
90
|
}
|
|
173
91
|
return fields;
|
|
174
92
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Extract business args and telemetry from tool call arguments.
|
|
177
|
-
*/
|
|
178
|
-
export function extractToolCallPayload(args) {
|
|
179
|
-
const { _diagnostic_telemetry, ...businessArgs } = args;
|
|
180
|
-
const telemetry = _diagnostic_telemetry && typeof _diagnostic_telemetry === "object"
|
|
181
|
-
? _diagnostic_telemetry
|
|
182
|
-
: null;
|
|
183
|
-
return { businessArgs, telemetry };
|
|
184
|
-
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { buildDiagnosticToolCall, deriveDiagnosticHints, } from "../diagnostics.js";
|
|
2
|
+
// implements REQ-002
|
|
3
|
+
export async function appendDiagnosticSuccessUsage(input) {
|
|
4
|
+
const finishedAt = new Date();
|
|
5
|
+
const diagnosticFields = input.runtime.deriveDiagnosticFields(input.toolName, input.businessArgs, input.telemetry, input.result);
|
|
6
|
+
const toolCall = buildDiagnosticToolCall({
|
|
7
|
+
tool: input.toolName,
|
|
8
|
+
requestId: input.requestId,
|
|
9
|
+
args: input.args,
|
|
10
|
+
diagnosticPhase: "success",
|
|
11
|
+
telemetry: input.telemetry,
|
|
12
|
+
});
|
|
13
|
+
const processHandle = await input.runtime.prologProcess();
|
|
14
|
+
const branchName = await input.runtime.activeBranchName();
|
|
15
|
+
input.runtime.appendUsageLogLine({
|
|
16
|
+
timestamp: finishedAt.toISOString(),
|
|
17
|
+
request_id: input.requestId,
|
|
18
|
+
tool: input.toolName,
|
|
19
|
+
telemetry: input.telemetry,
|
|
20
|
+
business_args: input.businessArgs,
|
|
21
|
+
tool_call: toolCall,
|
|
22
|
+
retry_key: toolCall.retry_key,
|
|
23
|
+
diagnostic_phase: toolCall.diagnostic_phase,
|
|
24
|
+
status: "success",
|
|
25
|
+
started_at: input.startedAt.toISOString(),
|
|
26
|
+
finished_at: finishedAt.toISOString(),
|
|
27
|
+
duration_ms: finishedAt.getTime() - input.startedAt.getTime(),
|
|
28
|
+
prolog_pid: processHandle?.getPid() ?? null,
|
|
29
|
+
active_branch: branchName,
|
|
30
|
+
...diagnosticFields,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// implements REQ-002
|
|
34
|
+
export async function appendDiagnosticErrorUsage(input) {
|
|
35
|
+
const finishedAt = new Date();
|
|
36
|
+
const err = input.error instanceof Error ? input.error : new Error(String(input.error));
|
|
37
|
+
const diagnosticErrorFields = input.runtime.classifyDiagnosticError(err);
|
|
38
|
+
const diagnosticHints = deriveDiagnosticHints({
|
|
39
|
+
tool: input.toolName,
|
|
40
|
+
error: err,
|
|
41
|
+
businessArgs: input.businessArgs,
|
|
42
|
+
});
|
|
43
|
+
const toolCall = buildDiagnosticToolCall({
|
|
44
|
+
tool: input.toolName,
|
|
45
|
+
requestId: input.requestId,
|
|
46
|
+
args: input.args,
|
|
47
|
+
diagnosticPhase: "error",
|
|
48
|
+
error: err,
|
|
49
|
+
telemetry: input.telemetry,
|
|
50
|
+
});
|
|
51
|
+
const processHandle = await input.runtime.prologProcess();
|
|
52
|
+
const branchName = await input.runtime.activeBranchName();
|
|
53
|
+
input.runtime.appendUsageLogLine({
|
|
54
|
+
timestamp: finishedAt.toISOString(),
|
|
55
|
+
request_id: input.requestId,
|
|
56
|
+
tool: input.toolName,
|
|
57
|
+
telemetry: input.telemetry,
|
|
58
|
+
business_args: input.businessArgs,
|
|
59
|
+
tool_call: toolCall,
|
|
60
|
+
retry_key: toolCall.retry_key,
|
|
61
|
+
diagnostic_phase: toolCall.diagnostic_phase,
|
|
62
|
+
status: "error",
|
|
63
|
+
started_at: input.startedAt.toISOString(),
|
|
64
|
+
finished_at: finishedAt.toISOString(),
|
|
65
|
+
duration_ms: finishedAt.getTime() - input.startedAt.getTime(),
|
|
66
|
+
prolog_pid: processHandle?.getPid() ?? null,
|
|
67
|
+
active_branch: branchName,
|
|
68
|
+
reset_attempted: input.resetState.resetAttempted,
|
|
69
|
+
reset_succeeded: input.resetState.resetSucceeded,
|
|
70
|
+
reset_error: input.resetState.resetError,
|
|
71
|
+
diagnostic_hints: [diagnosticHints],
|
|
72
|
+
...diagnosticErrorFields,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// implements REQ-002
|
|
3
|
+
export function jsonSchemaToZod(schema) {
|
|
4
|
+
if (!schema || typeof schema !== "object") {
|
|
5
|
+
return z.any();
|
|
6
|
+
}
|
|
7
|
+
const obj = schema;
|
|
8
|
+
if (Array.isArray(obj.enum) && obj.enum.length > 0) {
|
|
9
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
10
|
+
const literals = obj.enum.filter((value) => typeof value === "string" ||
|
|
11
|
+
typeof value === "number" ||
|
|
12
|
+
typeof value === "boolean" ||
|
|
13
|
+
value === null);
|
|
14
|
+
if (literals.length === 0) {
|
|
15
|
+
return description ? z.any().describe(description) : z.any();
|
|
16
|
+
}
|
|
17
|
+
const literalSchemas = literals.map((value) => z.literal(value));
|
|
18
|
+
if (literalSchemas.length === 1) {
|
|
19
|
+
const single = literalSchemas[0];
|
|
20
|
+
if (!single) {
|
|
21
|
+
return description ? z.any().describe(description) : z.any();
|
|
22
|
+
}
|
|
23
|
+
return description ? single.describe(description) : single;
|
|
24
|
+
}
|
|
25
|
+
const union = z.union(literalSchemas);
|
|
26
|
+
return description ? union.describe(description) : union;
|
|
27
|
+
}
|
|
28
|
+
const schemaType = typeof obj.type === "string" ? obj.type : undefined;
|
|
29
|
+
switch (schemaType) {
|
|
30
|
+
case "object": {
|
|
31
|
+
const properties = obj.properties && typeof obj.properties === "object"
|
|
32
|
+
? obj.properties
|
|
33
|
+
: {};
|
|
34
|
+
const required = new Set(Array.isArray(obj.required)
|
|
35
|
+
? obj.required.filter((k) => typeof k === "string" && k.length > 0)
|
|
36
|
+
: []);
|
|
37
|
+
const shape = {};
|
|
38
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
39
|
+
const propSchema = jsonSchemaToZod(value);
|
|
40
|
+
shape[key] = required.has(key) ? propSchema : propSchema.optional();
|
|
41
|
+
}
|
|
42
|
+
const objectSchema = obj.additionalProperties === false
|
|
43
|
+
? z.object(shape)
|
|
44
|
+
: z.looseObject(shape);
|
|
45
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
46
|
+
return description ? objectSchema.describe(description) : objectSchema;
|
|
47
|
+
}
|
|
48
|
+
case "array": {
|
|
49
|
+
const itemSchema = jsonSchemaToZod(obj.items);
|
|
50
|
+
let arraySchema = z.array(itemSchema);
|
|
51
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
52
|
+
if (typeof obj.minItems === "number") {
|
|
53
|
+
arraySchema = arraySchema.min(obj.minItems);
|
|
54
|
+
}
|
|
55
|
+
if (typeof obj.maxItems === "number") {
|
|
56
|
+
arraySchema = arraySchema.max(obj.maxItems);
|
|
57
|
+
}
|
|
58
|
+
return description ? arraySchema.describe(description) : arraySchema;
|
|
59
|
+
}
|
|
60
|
+
case "string": {
|
|
61
|
+
let s = z.string();
|
|
62
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
63
|
+
if (typeof obj.minLength === "number") {
|
|
64
|
+
s = s.min(obj.minLength);
|
|
65
|
+
}
|
|
66
|
+
if (typeof obj.maxLength === "number") {
|
|
67
|
+
s = s.max(obj.maxLength);
|
|
68
|
+
}
|
|
69
|
+
return description ? s.describe(description) : s;
|
|
70
|
+
}
|
|
71
|
+
case "number": {
|
|
72
|
+
let n = z.number();
|
|
73
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
74
|
+
if (typeof obj.minimum === "number") {
|
|
75
|
+
n = n.min(obj.minimum);
|
|
76
|
+
}
|
|
77
|
+
if (typeof obj.maximum === "number") {
|
|
78
|
+
n = n.max(obj.maximum);
|
|
79
|
+
}
|
|
80
|
+
return description ? n.describe(description) : n;
|
|
81
|
+
}
|
|
82
|
+
case "integer": {
|
|
83
|
+
let n = z.number().int();
|
|
84
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
85
|
+
if (typeof obj.minimum === "number") {
|
|
86
|
+
n = n.min(obj.minimum);
|
|
87
|
+
}
|
|
88
|
+
if (typeof obj.maximum === "number") {
|
|
89
|
+
n = n.max(obj.maximum);
|
|
90
|
+
}
|
|
91
|
+
return description ? n.describe(description) : n;
|
|
92
|
+
}
|
|
93
|
+
case "boolean": {
|
|
94
|
+
const b = z.boolean();
|
|
95
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
96
|
+
return description ? b.describe(description) : b;
|
|
97
|
+
}
|
|
98
|
+
default: {
|
|
99
|
+
const anySchema = z.any();
|
|
100
|
+
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
101
|
+
return description ? anySchema.describe(description) : anySchema;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/server/session.js
CHANGED
|
@@ -192,7 +192,7 @@ async function refreshAttachedBranchKbWithRetry(prolog, kbPath, currentStamp, as
|
|
|
192
192
|
return postAttachStamp;
|
|
193
193
|
}
|
|
194
194
|
function usesBranchKbPath(kbPath) {
|
|
195
|
-
return kbPath.includes("/.kb/branches/") || kbPath.includes("\\.kb\\branches\\");
|
|
195
|
+
return (kbPath.includes("/.kb/branches/") || kbPath.includes("\\.kb\\branches\\"));
|
|
196
196
|
}
|
|
197
197
|
// implements REQ-008
|
|
198
198
|
async function ensurePrologUnsafe() {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// implements REQ-002, REQ-013
|
|
2
|
+
export function registerConfiguredTools(server, runtime, registerTool) {
|
|
3
|
+
const toolDef = (name) => {
|
|
4
|
+
const t = runtime.tools.find((tool) => tool.name === name);
|
|
5
|
+
if (!t)
|
|
6
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
7
|
+
return t;
|
|
8
|
+
};
|
|
9
|
+
const register = ({ name, handler }) => {
|
|
10
|
+
const definition = toolDef(name);
|
|
11
|
+
registerTool(server, name, definition.description, definition.inputSchema, handler, runtime);
|
|
12
|
+
};
|
|
13
|
+
// INTENTIONAL ARGUMENT CASTS: The `args as (unknown as)? XyzArgs` casts below
|
|
14
|
+
// bridge the generic ToolHandler (which receives Record<string, unknown>) to the
|
|
15
|
+
// specific handler argument types. Argument shapes are validated by Zod schemas
|
|
16
|
+
// (via jsonSchemaToZod) before the handler is invoked, so the casts are safe at runtime.
|
|
17
|
+
register({
|
|
18
|
+
name: "kb_query",
|
|
19
|
+
handler: async (args) => {
|
|
20
|
+
const prolog = await runtime.ensureProlog();
|
|
21
|
+
return runtime.handleKbQuery(prolog, args);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
register({
|
|
25
|
+
name: "kb_search",
|
|
26
|
+
handler: async (args) => {
|
|
27
|
+
const prolog = await runtime.ensureProlog();
|
|
28
|
+
return runtime.handleKbSearch(prolog, args);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
register({
|
|
32
|
+
name: "kb_status",
|
|
33
|
+
handler: async (args) => {
|
|
34
|
+
const prolog = await runtime.ensureProlog();
|
|
35
|
+
return runtime.handleKbStatus(prolog, args);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
register({
|
|
39
|
+
name: "kb_skills_list",
|
|
40
|
+
handler: async (args) => runtime.handleKbSkillsList(args),
|
|
41
|
+
});
|
|
42
|
+
register({
|
|
43
|
+
name: "kb_skills_load",
|
|
44
|
+
handler: async (args) => runtime.handleKbSkillsLoad(args),
|
|
45
|
+
});
|
|
46
|
+
register({
|
|
47
|
+
name: "kb_skills_read",
|
|
48
|
+
handler: async (args) => runtime.handleKbSkillsRead(args),
|
|
49
|
+
});
|
|
50
|
+
register({
|
|
51
|
+
name: "kb_find_gaps",
|
|
52
|
+
handler: async (args) => {
|
|
53
|
+
const prolog = await runtime.ensureProlog();
|
|
54
|
+
return runtime.handleKbFindGaps(prolog, args);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
register({
|
|
58
|
+
name: "kb_coverage",
|
|
59
|
+
handler: async (args) => {
|
|
60
|
+
const prolog = await runtime.ensureProlog();
|
|
61
|
+
return runtime.handleKbCoverage(prolog, args);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
register({
|
|
65
|
+
name: "kb_graph",
|
|
66
|
+
handler: async (args) => {
|
|
67
|
+
const prolog = await runtime.ensureProlog();
|
|
68
|
+
return runtime.handleKbGraph(prolog, args);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
register({
|
|
72
|
+
name: "kb_sparql_remote",
|
|
73
|
+
handler: async (args) => {
|
|
74
|
+
const prolog = await runtime.ensureProlog();
|
|
75
|
+
return runtime.handleSparql(prolog, args);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
register({
|
|
79
|
+
name: "kb_semantic_advisor",
|
|
80
|
+
handler: async (args) => runtime.handleKbSemanticAdvisor(args),
|
|
81
|
+
});
|
|
82
|
+
register({
|
|
83
|
+
name: "kb_upsert",
|
|
84
|
+
handler: async (args) => {
|
|
85
|
+
const prolog = await runtime.ensureProlog();
|
|
86
|
+
return runtime.handleKbUpsert(prolog, args);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
register({
|
|
90
|
+
name: "kb_validate_upsert",
|
|
91
|
+
handler: async (args) => runtime.handleKbValidateUpsert(args),
|
|
92
|
+
});
|
|
93
|
+
register({
|
|
94
|
+
name: "kb_delete",
|
|
95
|
+
handler: async (args) => {
|
|
96
|
+
const prolog = await runtime.ensureProlog();
|
|
97
|
+
return runtime.handleKbDelete(prolog, args);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
register({
|
|
101
|
+
name: "kb_check",
|
|
102
|
+
handler: async (args) => {
|
|
103
|
+
const prolog = await runtime.ensureProlog();
|
|
104
|
+
return runtime.handleKbCheck(prolog, args);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
register({
|
|
108
|
+
name: "kb_model_requirement",
|
|
109
|
+
handler: async (args) => {
|
|
110
|
+
const prolog = await runtime.ensureProlog();
|
|
111
|
+
return runtime.handleKbModelRequirement(prolog, args);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
register({
|
|
115
|
+
name: "kb_suggest_predicates",
|
|
116
|
+
handler: async (args) => {
|
|
117
|
+
const prolog = await runtime.ensureProlog();
|
|
118
|
+
return runtime.handleKbSuggestPredicates(prolog, args);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
register({
|
|
122
|
+
name: "kb_autopilot_generate",
|
|
123
|
+
handler: async (args) => {
|
|
124
|
+
const prolog = await runtime.ensureProlog();
|
|
125
|
+
return runtime.handleKbAutopilotGenerate(prolog, args);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DIAGNOSTIC_MODE_ENABLED, appendUsageLogLine, classifyDiagnosticError, deriveDiagnosticFields, extractToolCallPayload, } from "../diagnostics.js";
|
|
2
|
+
import { TOOLS } from "../tools-config.js";
|
|
3
|
+
import { handleKbAutopilotGenerate } from "../tools/autopilot-generate.js";
|
|
4
|
+
import { handleKbCheck } from "../tools/check.js";
|
|
5
|
+
import { handleKbCoverage } from "../tools/coverage.js";
|
|
6
|
+
import { handleKbDelete } from "../tools/delete.js";
|
|
7
|
+
import { handleKbFindGaps } from "../tools/find-gaps.js";
|
|
8
|
+
import { handleKbGraph } from "../tools/graph.js";
|
|
9
|
+
import { handleKbModelRequirement } from "../tools/model-requirement.js";
|
|
10
|
+
import { handleKbQuery } from "../tools/query.js";
|
|
11
|
+
import { handleKbSearch } from "../tools/search.js";
|
|
12
|
+
import { handleKbSemanticAdvisor } from "../tools/semantic-advisor.js";
|
|
13
|
+
import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
|
|
14
|
+
import { handleSparql } from "../tools/sparql.js";
|
|
15
|
+
import { handleKbStatus } from "../tools/status.js";
|
|
16
|
+
import { handleKbSuggestPredicates } from "../tools/suggest-predicates.js";
|
|
17
|
+
import { handleKbUpsert } from "../tools/upsert.js";
|
|
18
|
+
import { handleKbValidateUpsert } from "../tools/validate-upsert.js";
|
|
19
|
+
const defaultToolsServerDeps = {
|
|
20
|
+
getSessionModule: () => import("./session.js"),
|
|
21
|
+
};
|
|
22
|
+
let sessionModulePromise = null;
|
|
23
|
+
// implements REQ-008
|
|
24
|
+
export function _setToolsServerDepsForTests(deps, resetPromise = false) {
|
|
25
|
+
defaultToolsServerDeps.getSessionModule =
|
|
26
|
+
deps.getSessionModule ?? defaultToolsServerDeps.getSessionModule;
|
|
27
|
+
if (resetPromise) {
|
|
28
|
+
sessionModulePromise = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// implements REQ-012
|
|
32
|
+
export function _resetSessionModulePromise() {
|
|
33
|
+
sessionModulePromise = null;
|
|
34
|
+
}
|
|
35
|
+
/* v8 ignore next (3 lines) — lazy async module loader; body only executes once per process
|
|
36
|
+
* when DEFAULT_TOOLS_RUNTIME.activeBranchName/ensureProlog/etc. are first called.
|
|
37
|
+
* Cannot be re-triggered without process restart (sessionModulePromise is module-level). */
|
|
38
|
+
async function getSessionModule() {
|
|
39
|
+
sessionModulePromise ??= defaultToolsServerDeps.getSessionModule();
|
|
40
|
+
return sessionModulePromise;
|
|
41
|
+
}
|
|
42
|
+
export const DEFAULT_TOOLS_RUNTIME = {
|
|
43
|
+
diagnosticModeEnabled: () => DIAGNOSTIC_MODE_ENABLED,
|
|
44
|
+
appendUsageLogLine,
|
|
45
|
+
classifyDiagnosticError,
|
|
46
|
+
deriveDiagnosticFields,
|
|
47
|
+
extractToolCallPayload,
|
|
48
|
+
// INTENTIONAL: TOOLS is imported as a Zod-inferred schema type; ToolConfig is the
|
|
49
|
+
// runtime interface with looser Record<string, unknown> inputSchema. The cast is safe
|
|
50
|
+
// because the tool definitions are statically authored and validated at startup.
|
|
51
|
+
tools: TOOLS,
|
|
52
|
+
activeBranchName: async () => (await getSessionModule()).activeBranchName,
|
|
53
|
+
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
54
|
+
resetProlog: async (reason) => (await getSessionModule()).resetProlog(reason),
|
|
55
|
+
inFlightRequests: async () => (await getSessionModule()).inFlightRequests,
|
|
56
|
+
isShuttingDown: async () => (await getSessionModule()).isShuttingDown,
|
|
57
|
+
prologProcess: async () => (await getSessionModule()).prologProcess,
|
|
58
|
+
handleKbCheck,
|
|
59
|
+
handleKbCoverage,
|
|
60
|
+
handleKbDelete,
|
|
61
|
+
handleKbFindGaps,
|
|
62
|
+
handleKbGraph,
|
|
63
|
+
handleSparql,
|
|
64
|
+
handleKbQuery,
|
|
65
|
+
handleKbSearch,
|
|
66
|
+
handleKbStatus,
|
|
67
|
+
handleKbSemanticAdvisor,
|
|
68
|
+
handleKbSkillsList,
|
|
69
|
+
handleKbSkillsLoad,
|
|
70
|
+
handleKbSkillsRead,
|
|
71
|
+
handleKbUpsert,
|
|
72
|
+
handleKbValidateUpsert,
|
|
73
|
+
handleKbModelRequirement,
|
|
74
|
+
handleKbSuggestPredicates,
|
|
75
|
+
handleKbAutopilotGenerate,
|
|
76
|
+
};
|
package/dist/server/tools.js
CHANGED
|
@@ -16,86 +16,15 @@
|
|
|
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 { z } from "zod";
|
|
20
|
-
import { DIAGNOSTIC_MODE_ENABLED, appendUsageLogLine, classifyDiagnosticError, deriveDiagnosticFields, extractToolCallPayload, } from "../diagnostics.js";
|
|
21
19
|
import { isMcpDebugEnabled } from "../env.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import { handleKbGraph } from "../tools/graph.js";
|
|
29
|
-
import { handleKbModelRequirement, } from "../tools/model-requirement.js";
|
|
30
|
-
import { handleKbQuery } from "../tools/query.js";
|
|
31
|
-
import { handleKbSearch } from "../tools/search.js";
|
|
32
|
-
import { handleKbSemanticAdvisor, } from "../tools/semantic-advisor.js";
|
|
33
|
-
import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
|
|
34
|
-
import { handleSparql } from "../tools/sparql.js";
|
|
35
|
-
import { handleKbStatus } from "../tools/status.js";
|
|
36
|
-
import { handleKbSuggestPredicates, } from "../tools/suggest-predicates.js";
|
|
37
|
-
import { handleKbUpsert } from "../tools/upsert.js";
|
|
38
|
-
import { handleKbValidateUpsert } from "../tools/validate-upsert.js";
|
|
20
|
+
import { appendDiagnosticErrorUsage, appendDiagnosticSuccessUsage, } from "./diagnostic-usage.js";
|
|
21
|
+
import { jsonSchemaToZod } from "./json-schema-to-zod.js";
|
|
22
|
+
import { registerConfiguredTools } from "./tool-registration.js";
|
|
23
|
+
import { DEFAULT_TOOLS_RUNTIME, _resetSessionModulePromise, _setToolsServerDepsForTests, } from "./tools-runtime.js";
|
|
24
|
+
export { jsonSchemaToZod } from "./json-schema-to-zod.js";
|
|
25
|
+
export { _resetSessionModulePromise, _setToolsServerDepsForTests };
|
|
39
26
|
const DEFAULT_TOOL_TIMEOUT_MS = 90_000;
|
|
40
27
|
const TOOL_TIMEOUT_ENV = "KIBI_MCP_TOOL_TIMEOUT_MS";
|
|
41
|
-
const defaultToolsServerDeps = {
|
|
42
|
-
getSessionModule: () => import("./session.js"),
|
|
43
|
-
};
|
|
44
|
-
// implements REQ-008
|
|
45
|
-
export function _setToolsServerDepsForTests(deps, resetPromise = false) {
|
|
46
|
-
defaultToolsServerDeps.getSessionModule =
|
|
47
|
-
deps.getSessionModule ?? defaultToolsServerDeps.getSessionModule;
|
|
48
|
-
if (resetPromise) {
|
|
49
|
-
sessionModulePromise = null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// implements REQ-012
|
|
53
|
-
export function _resetSessionModulePromise() {
|
|
54
|
-
sessionModulePromise = null;
|
|
55
|
-
}
|
|
56
|
-
let sessionModulePromise = null;
|
|
57
|
-
/* v8 ignore next (3 lines) — lazy async module loader; body only executes once per process
|
|
58
|
-
* when DEFAULT_TOOLS_RUNTIME.activeBranchName/ensureProlog/etc. are first called.
|
|
59
|
-
* Cannot be re-triggered without process restart (sessionModulePromise is module-level). */
|
|
60
|
-
async function getSessionModule() {
|
|
61
|
-
sessionModulePromise ??= defaultToolsServerDeps.getSessionModule();
|
|
62
|
-
return sessionModulePromise;
|
|
63
|
-
}
|
|
64
|
-
const DEFAULT_TOOLS_RUNTIME = {
|
|
65
|
-
diagnosticModeEnabled: () => DIAGNOSTIC_MODE_ENABLED,
|
|
66
|
-
appendUsageLogLine,
|
|
67
|
-
classifyDiagnosticError,
|
|
68
|
-
deriveDiagnosticFields,
|
|
69
|
-
extractToolCallPayload,
|
|
70
|
-
// INTENTIONAL: TOOLS is imported as a Zod-inferred schema type; ToolConfig is the
|
|
71
|
-
// runtime interface with looser Record<string, unknown> inputSchema. The cast is safe
|
|
72
|
-
// because the tool definitions are statically authored and validated at startup.
|
|
73
|
-
tools: TOOLS,
|
|
74
|
-
activeBranchName: async () => (await getSessionModule()).activeBranchName,
|
|
75
|
-
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
76
|
-
resetProlog: async (reason) => (await getSessionModule()).resetProlog(reason),
|
|
77
|
-
inFlightRequests: async () => (await getSessionModule()).inFlightRequests,
|
|
78
|
-
isShuttingDown: async () => (await getSessionModule()).isShuttingDown,
|
|
79
|
-
prologProcess: async () => (await getSessionModule()).prologProcess,
|
|
80
|
-
handleKbCheck,
|
|
81
|
-
handleKbCoverage,
|
|
82
|
-
handleKbDelete,
|
|
83
|
-
handleKbFindGaps,
|
|
84
|
-
handleKbGraph,
|
|
85
|
-
handleSparql,
|
|
86
|
-
handleKbQuery,
|
|
87
|
-
handleKbSearch,
|
|
88
|
-
handleKbStatus,
|
|
89
|
-
handleKbSemanticAdvisor,
|
|
90
|
-
handleKbSkillsList,
|
|
91
|
-
handleKbSkillsLoad,
|
|
92
|
-
handleKbSkillsRead,
|
|
93
|
-
handleKbUpsert,
|
|
94
|
-
handleKbValidateUpsert,
|
|
95
|
-
handleKbModelRequirement,
|
|
96
|
-
handleKbSuggestPredicates,
|
|
97
|
-
handleKbAutopilotGenerate,
|
|
98
|
-
};
|
|
99
28
|
// implements REQ-008
|
|
100
29
|
function debugLog(...args) {
|
|
101
30
|
if (isMcpDebugEnabled()) {
|
|
@@ -140,109 +69,6 @@ async function withToolTimeout(toolName, operation, onTimeout) {
|
|
|
140
69
|
}
|
|
141
70
|
}
|
|
142
71
|
// implements REQ-002
|
|
143
|
-
export function jsonSchemaToZod(schema) {
|
|
144
|
-
if (!schema || typeof schema !== "object") {
|
|
145
|
-
return z.any();
|
|
146
|
-
}
|
|
147
|
-
const obj = schema;
|
|
148
|
-
if (Array.isArray(obj.enum) && obj.enum.length > 0) {
|
|
149
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
150
|
-
const literals = obj.enum.filter((value) => typeof value === "string" ||
|
|
151
|
-
typeof value === "number" ||
|
|
152
|
-
typeof value === "boolean" ||
|
|
153
|
-
value === null);
|
|
154
|
-
if (literals.length === 0) {
|
|
155
|
-
return description ? z.any().describe(description) : z.any();
|
|
156
|
-
}
|
|
157
|
-
const literalSchemas = literals.map((value) => z.literal(value));
|
|
158
|
-
if (literalSchemas.length === 1) {
|
|
159
|
-
const single = literalSchemas[0];
|
|
160
|
-
if (!single) {
|
|
161
|
-
return description ? z.any().describe(description) : z.any();
|
|
162
|
-
}
|
|
163
|
-
return description ? single.describe(description) : single;
|
|
164
|
-
}
|
|
165
|
-
const union = z.union(literalSchemas);
|
|
166
|
-
return description ? union.describe(description) : union;
|
|
167
|
-
}
|
|
168
|
-
const schemaType = typeof obj.type === "string" ? obj.type : undefined;
|
|
169
|
-
switch (schemaType) {
|
|
170
|
-
case "object": {
|
|
171
|
-
const properties = obj.properties && typeof obj.properties === "object"
|
|
172
|
-
? obj.properties
|
|
173
|
-
: {};
|
|
174
|
-
const required = new Set(Array.isArray(obj.required)
|
|
175
|
-
? obj.required.filter((k) => typeof k === "string" && k.length > 0)
|
|
176
|
-
: []);
|
|
177
|
-
const shape = {};
|
|
178
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
179
|
-
const propSchema = jsonSchemaToZod(value);
|
|
180
|
-
shape[key] = required.has(key) ? propSchema : propSchema.optional();
|
|
181
|
-
}
|
|
182
|
-
const objectSchema = obj.additionalProperties === false
|
|
183
|
-
? z.object(shape)
|
|
184
|
-
: z.looseObject(shape);
|
|
185
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
186
|
-
return description ? objectSchema.describe(description) : objectSchema;
|
|
187
|
-
}
|
|
188
|
-
case "array": {
|
|
189
|
-
const itemSchema = jsonSchemaToZod(obj.items);
|
|
190
|
-
let arraySchema = z.array(itemSchema);
|
|
191
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
192
|
-
if (typeof obj.minItems === "number") {
|
|
193
|
-
arraySchema = arraySchema.min(obj.minItems);
|
|
194
|
-
}
|
|
195
|
-
if (typeof obj.maxItems === "number") {
|
|
196
|
-
arraySchema = arraySchema.max(obj.maxItems);
|
|
197
|
-
}
|
|
198
|
-
return description ? arraySchema.describe(description) : arraySchema;
|
|
199
|
-
}
|
|
200
|
-
case "string": {
|
|
201
|
-
let s = z.string();
|
|
202
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
203
|
-
if (typeof obj.minLength === "number") {
|
|
204
|
-
s = s.min(obj.minLength);
|
|
205
|
-
}
|
|
206
|
-
if (typeof obj.maxLength === "number") {
|
|
207
|
-
s = s.max(obj.maxLength);
|
|
208
|
-
}
|
|
209
|
-
return description ? s.describe(description) : s;
|
|
210
|
-
}
|
|
211
|
-
case "number": {
|
|
212
|
-
let n = z.number();
|
|
213
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
214
|
-
if (typeof obj.minimum === "number") {
|
|
215
|
-
n = n.min(obj.minimum);
|
|
216
|
-
}
|
|
217
|
-
if (typeof obj.maximum === "number") {
|
|
218
|
-
n = n.max(obj.maximum);
|
|
219
|
-
}
|
|
220
|
-
return description ? n.describe(description) : n;
|
|
221
|
-
}
|
|
222
|
-
case "integer": {
|
|
223
|
-
let n = z.number().int();
|
|
224
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
225
|
-
if (typeof obj.minimum === "number") {
|
|
226
|
-
n = n.min(obj.minimum);
|
|
227
|
-
}
|
|
228
|
-
if (typeof obj.maximum === "number") {
|
|
229
|
-
n = n.max(obj.maximum);
|
|
230
|
-
}
|
|
231
|
-
return description ? n.describe(description) : n;
|
|
232
|
-
}
|
|
233
|
-
case "boolean": {
|
|
234
|
-
const b = z.boolean();
|
|
235
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
236
|
-
return description ? b.describe(description) : b;
|
|
237
|
-
}
|
|
238
|
-
default: {
|
|
239
|
-
const anySchema = z.any();
|
|
240
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
241
|
-
return description ? anySchema.describe(description) : anySchema;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// implements REQ-002
|
|
246
72
|
export function addTool(server, name, description, inputSchema, handler,
|
|
247
73
|
// INTENTIONAL: DEFAULT_TOOLS_RUNTIME is typed as ToolsRuntime<PrologProcess>; the
|
|
248
74
|
// generic TProlog parameter exists so tests can inject a mock type. The cast is safe
|
|
@@ -293,23 +119,15 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
293
119
|
});
|
|
294
120
|
// Log usage in diagnostic mode
|
|
295
121
|
if (diagnosticModeEnabled) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
request_id: requestId,
|
|
303
|
-
tool: name,
|
|
122
|
+
await appendDiagnosticSuccessUsage({
|
|
123
|
+
runtime,
|
|
124
|
+
toolName: name,
|
|
125
|
+
requestId,
|
|
126
|
+
args,
|
|
127
|
+
businessArgs,
|
|
304
128
|
telemetry,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
started_at: startedAt.toISOString(),
|
|
308
|
-
finished_at: finishedAt.toISOString(),
|
|
309
|
-
duration_ms: finishedAt.getTime() - startedAt.getTime(),
|
|
310
|
-
prolog_pid: processHandle?.getPid() ?? null,
|
|
311
|
-
active_branch: branchName,
|
|
312
|
-
...diagnosticFields,
|
|
129
|
+
startedAt,
|
|
130
|
+
result,
|
|
313
131
|
});
|
|
314
132
|
}
|
|
315
133
|
return result;
|
|
@@ -317,27 +135,16 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
317
135
|
catch (error) {
|
|
318
136
|
// Log error in diagnostic mode
|
|
319
137
|
if (diagnosticModeEnabled) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
timestamp: finishedAt.toISOString(),
|
|
327
|
-
request_id: requestId,
|
|
328
|
-
tool: name,
|
|
138
|
+
await appendDiagnosticErrorUsage({
|
|
139
|
+
runtime,
|
|
140
|
+
toolName: name,
|
|
141
|
+
requestId,
|
|
142
|
+
args,
|
|
143
|
+
businessArgs,
|
|
329
144
|
telemetry,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
finished_at: finishedAt.toISOString(),
|
|
334
|
-
duration_ms: finishedAt.getTime() - startedAt.getTime(),
|
|
335
|
-
prolog_pid: processHandle?.getPid() ?? null,
|
|
336
|
-
active_branch: branchName,
|
|
337
|
-
reset_attempted: resetAttempted,
|
|
338
|
-
reset_succeeded: resetSucceeded,
|
|
339
|
-
reset_error: resetError,
|
|
340
|
-
...diagnosticErrorFields,
|
|
145
|
+
startedAt,
|
|
146
|
+
error,
|
|
147
|
+
resetState: { resetAttempted, resetSucceeded, resetError },
|
|
341
148
|
});
|
|
342
149
|
}
|
|
343
150
|
throw error;
|
|
@@ -362,75 +169,5 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
362
169
|
export function registerAllTools(server,
|
|
363
170
|
// INTENTIONAL: same generic bridge cast as addTool — see comment there.
|
|
364
171
|
runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
365
|
-
|
|
366
|
-
const t = runtime.tools.find((tool) => tool.name === name);
|
|
367
|
-
if (!t)
|
|
368
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
369
|
-
return t;
|
|
370
|
-
};
|
|
371
|
-
// INTENTIONAL ARGUMENT CASTS: The `args as (unknown as)? XyzArgs` casts below
|
|
372
|
-
// bridge the generic ToolHandler (which receives Record<string, unknown>) to the
|
|
373
|
-
// specific handler argument types. Argument shapes are validated by Zod schemas
|
|
374
|
-
// (via jsonSchemaToZod) before the handler is invoked, so the casts are safe at runtime.
|
|
375
|
-
addTool(server, "kb_query", toolDef("kb_query").description, toolDef("kb_query").inputSchema, async (args) => {
|
|
376
|
-
const prolog = await runtime.ensureProlog();
|
|
377
|
-
return runtime.handleKbQuery(prolog, args);
|
|
378
|
-
}, runtime);
|
|
379
|
-
addTool(server, "kb_search", toolDef("kb_search").description, toolDef("kb_search").inputSchema, async (args) => {
|
|
380
|
-
const prolog = await runtime.ensureProlog();
|
|
381
|
-
return runtime.handleKbSearch(prolog, args);
|
|
382
|
-
}, runtime);
|
|
383
|
-
addTool(server, "kb_status", toolDef("kb_status").description, toolDef("kb_status").inputSchema, async (args) => {
|
|
384
|
-
const prolog = await runtime.ensureProlog();
|
|
385
|
-
return runtime.handleKbStatus(prolog, args);
|
|
386
|
-
}, runtime);
|
|
387
|
-
addTool(server, "kb_skills_list", toolDef("kb_skills_list").description, toolDef("kb_skills_list").inputSchema, async (args) => runtime.handleKbSkillsList(args), runtime);
|
|
388
|
-
addTool(server, "kb_skills_load", toolDef("kb_skills_load").description, toolDef("kb_skills_load").inputSchema, async (args) => runtime.handleKbSkillsLoad(args), runtime);
|
|
389
|
-
addTool(server, "kb_skills_read", toolDef("kb_skills_read").description, toolDef("kb_skills_read").inputSchema, async (args) => runtime.handleKbSkillsRead(args), runtime);
|
|
390
|
-
addTool(server, "kb_find_gaps", toolDef("kb_find_gaps").description, toolDef("kb_find_gaps").inputSchema, async (args) => {
|
|
391
|
-
const prolog = await runtime.ensureProlog();
|
|
392
|
-
return runtime.handleKbFindGaps(prolog, args);
|
|
393
|
-
}, runtime);
|
|
394
|
-
addTool(server, "kb_coverage", toolDef("kb_coverage").description, toolDef("kb_coverage").inputSchema, async (args) => {
|
|
395
|
-
const prolog = await runtime.ensureProlog();
|
|
396
|
-
return runtime.handleKbCoverage(prolog, args);
|
|
397
|
-
}, runtime);
|
|
398
|
-
addTool(server, "kb_graph", toolDef("kb_graph").description, toolDef("kb_graph").inputSchema, async (args) => {
|
|
399
|
-
const prolog = await runtime.ensureProlog();
|
|
400
|
-
return runtime.handleKbGraph(prolog, args);
|
|
401
|
-
}, runtime);
|
|
402
|
-
addTool(server, "kb_sparql_remote", toolDef("kb_sparql_remote").description, toolDef("kb_sparql_remote").inputSchema, async (args) => {
|
|
403
|
-
const prolog = await runtime.ensureProlog();
|
|
404
|
-
return runtime.handleSparql(prolog, args);
|
|
405
|
-
}, runtime);
|
|
406
|
-
addTool(server, "kb_semantic_advisor", toolDef("kb_semantic_advisor").description, toolDef("kb_semantic_advisor").inputSchema, async (args) => {
|
|
407
|
-
return runtime.handleKbSemanticAdvisor(args);
|
|
408
|
-
}, runtime);
|
|
409
|
-
addTool(server, "kb_upsert", toolDef("kb_upsert").description, toolDef("kb_upsert").inputSchema, async (args) => {
|
|
410
|
-
const prolog = await runtime.ensureProlog();
|
|
411
|
-
return runtime.handleKbUpsert(prolog, args);
|
|
412
|
-
}, runtime);
|
|
413
|
-
addTool(server, "kb_validate_upsert", toolDef("kb_validate_upsert").description, toolDef("kb_validate_upsert").inputSchema, async (args) => {
|
|
414
|
-
return runtime.handleKbValidateUpsert(args);
|
|
415
|
-
}, runtime);
|
|
416
|
-
addTool(server, "kb_delete", toolDef("kb_delete").description, toolDef("kb_delete").inputSchema, async (args) => {
|
|
417
|
-
const prolog = await runtime.ensureProlog();
|
|
418
|
-
return runtime.handleKbDelete(prolog, args);
|
|
419
|
-
}, runtime);
|
|
420
|
-
addTool(server, "kb_check", toolDef("kb_check").description, toolDef("kb_check").inputSchema, async (args) => {
|
|
421
|
-
const prolog = await runtime.ensureProlog();
|
|
422
|
-
return runtime.handleKbCheck(prolog, args);
|
|
423
|
-
}, runtime);
|
|
424
|
-
addTool(server, "kb_model_requirement", toolDef("kb_model_requirement").description, toolDef("kb_model_requirement").inputSchema, async (args) => {
|
|
425
|
-
const prolog = await runtime.ensureProlog();
|
|
426
|
-
return runtime.handleKbModelRequirement(prolog, args);
|
|
427
|
-
}, runtime);
|
|
428
|
-
addTool(server, "kb_suggest_predicates", toolDef("kb_suggest_predicates").description, toolDef("kb_suggest_predicates").inputSchema, async (args) => {
|
|
429
|
-
const prolog = await runtime.ensureProlog();
|
|
430
|
-
return runtime.handleKbSuggestPredicates(prolog, args);
|
|
431
|
-
}, runtime);
|
|
432
|
-
addTool(server, "kb_autopilot_generate", toolDef("kb_autopilot_generate").description, toolDef("kb_autopilot_generate").inputSchema, async (args) => {
|
|
433
|
-
const prolog = await runtime.ensureProlog();
|
|
434
|
-
return runtime.handleKbAutopilotGenerate(prolog, args);
|
|
435
|
-
}, runtime);
|
|
172
|
+
registerConfiguredTools(server, runtime, addTool);
|
|
436
173
|
}
|
package/dist/tools/upsert.js
CHANGED
|
@@ -25,9 +25,9 @@ import relationshipSchema from "kibi-cli/schemas/relationship";
|
|
|
25
25
|
import { Project, ScriptKind } from "ts-morph";
|
|
26
26
|
import { isMcpDebugEnabled } from "../env.js";
|
|
27
27
|
import { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
|
|
28
|
-
import { refreshCoordinatesForSymbolId } from "./symbols.js";
|
|
29
|
-
import { attachedBranchKbPath, updateAttachedBranchStamp, } from "../server/session.js";
|
|
30
28
|
import { readBranchKbStamp } from "../server/kb-freshness.js";
|
|
29
|
+
import { attachedBranchKbPath, updateAttachedBranchStamp, } from "../server/session.js";
|
|
30
|
+
import { refreshCoordinatesForSymbolId } from "./symbols.js";
|
|
31
31
|
let refreshCoordinatesForSymbolIdImpl = refreshCoordinatesForSymbolId;
|
|
32
32
|
const ajv = new Ajv({ strict: false });
|
|
33
33
|
const entitySchemaRecord = entitySchema;
|
|
@@ -191,7 +191,8 @@ export async function handleKbUpsert(prolog, args) {
|
|
|
191
191
|
let relationshipsCreated = 0;
|
|
192
192
|
try {
|
|
193
193
|
if ((args.relationships === undefined ||
|
|
194
|
-
(Array.isArray(args.relationships) &&
|
|
194
|
+
(Array.isArray(args.relationships) &&
|
|
195
|
+
args.relationships.length === 0)) &&
|
|
195
196
|
args.id) {
|
|
196
197
|
// Preserve relationships on updates when the request omits relationships
|
|
197
198
|
// OR provides an empty array. This avoids accidental edge deletion by
|
|
@@ -476,7 +477,9 @@ function validateSymbolGranularity(entity, relationships) {
|
|
|
476
477
|
const shownNonBehavioral = nonBehavioralNames.slice(0, maxNamesInMessage);
|
|
477
478
|
const hiddenNonBehavioralCount = nonBehavioralNames.length - shownNonBehavioral.length;
|
|
478
479
|
const nonBehavioralList = shownNonBehavioral.join(", ") +
|
|
479
|
-
(hiddenNonBehavioralCount > 0
|
|
480
|
+
(hiddenNonBehavioralCount > 0
|
|
481
|
+
? `, and ${hiddenNonBehavioralCount} more`
|
|
482
|
+
: "");
|
|
480
483
|
const ignoredSymbolsMessage = nonBehavioralNames.length > 0
|
|
481
484
|
? ` Non-behavioral symbols in the file were ignored for this decision: ${nonBehavioralList}.`
|
|
482
485
|
: "";
|
|
@@ -653,10 +656,22 @@ async function checkScenarioCoverageGuidance(prolog, relationships, entityType,
|
|
|
653
656
|
*/
|
|
654
657
|
async function fetchExistingRelationships(prolog, entityId) {
|
|
655
658
|
const relTypes = [
|
|
656
|
-
"depends_on",
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
659
|
+
"depends_on",
|
|
660
|
+
"specified_by",
|
|
661
|
+
"verified_by",
|
|
662
|
+
"validates",
|
|
663
|
+
"implements",
|
|
664
|
+
"covered_by",
|
|
665
|
+
"executable_for",
|
|
666
|
+
"constrained_by",
|
|
667
|
+
"constrains",
|
|
668
|
+
"requires_property",
|
|
669
|
+
"requires_predicate",
|
|
670
|
+
"guards",
|
|
671
|
+
"publishes",
|
|
672
|
+
"consumes",
|
|
673
|
+
"supersedes",
|
|
674
|
+
"relates_to",
|
|
660
675
|
];
|
|
661
676
|
const existing = [];
|
|
662
677
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.5",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"fast-glob": "^3.2.12",
|
|
10
10
|
"gray-matter": "^4.0.3",
|
|
11
11
|
"js-yaml": "^4.1.0",
|
|
12
|
-
"kibi-cli": "^0.12.
|
|
13
|
-
"kibi-core": "^0.6.
|
|
12
|
+
"kibi-cli": "^0.12.8",
|
|
13
|
+
"kibi-core": "^0.6.4",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|
|
16
16
|
"zod": "^4.3.6"
|
|
@@ -27,10 +27,7 @@
|
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
28
|
"prepack": "npm run build"
|
|
29
29
|
},
|
|
30
|
-
"files": [
|
|
31
|
-
"dist",
|
|
32
|
-
"bin"
|
|
33
|
-
],
|
|
30
|
+
"files": ["dist", "bin"],
|
|
34
31
|
"engines": {
|
|
35
32
|
"node": ">=18",
|
|
36
33
|
"bun": ">=1.0"
|