kibi-mcp 0.1.5 → 0.2.0
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/server.js +112 -102
- package/dist/tools/check.js +17 -1
- package/dist/tools/context.js +2 -12
- package/dist/tools/delete.js +4 -1
- package/dist/tools/upsert.js +7 -6
- package/dist/tools-config.js +10 -206
- package/package.json +6 -7
package/dist/server.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
18
20
|
/*
|
|
19
21
|
How to apply this header to source files (examples)
|
|
20
22
|
|
|
@@ -46,23 +48,15 @@ import process from "node:process";
|
|
|
46
48
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
47
49
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
48
50
|
import { PrologProcess } from "kibi-cli/prolog";
|
|
51
|
+
import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, } from "kibi-cli/public/branch-resolver";
|
|
49
52
|
import { z } from "zod";
|
|
50
53
|
import { loadDefaultEnvFile } from "./env.js";
|
|
51
54
|
import { attachMcpcat } from "./mcpcat.js";
|
|
52
55
|
import { TOOLS } from "./tools-config.js";
|
|
53
|
-
import { handleKbBranchEnsure, handleKbBranchGc, } from "./tools/branch.js";
|
|
54
56
|
import { handleKbCheck } from "./tools/check.js";
|
|
55
|
-
import { handleKbContext } from "./tools/context.js";
|
|
56
|
-
import { handleKbCoverageReport, } from "./tools/coverage-report.js";
|
|
57
57
|
import { handleKbDelete } from "./tools/delete.js";
|
|
58
|
-
import { handleKbDerive } from "./tools/derive.js";
|
|
59
|
-
import { handleKbImpact } from "./tools/impact.js";
|
|
60
|
-
import { handleKbListEntityTypes, handleKbListRelationshipTypes, } from "./tools/list-types.js";
|
|
61
|
-
import { handleKbQueryRelationships, } from "./tools/query-relationships.js";
|
|
62
58
|
import { handleKbQuery } from "./tools/query.js";
|
|
63
59
|
import { handleKbUpsert } from "./tools/upsert.js";
|
|
64
|
-
import { handleKbSymbolsRefresh, } from "./tools/symbols.js";
|
|
65
|
-
import { handleSuggestSharedFacts, } from "./tools/suggest-shared-facts.js";
|
|
66
60
|
import { resolveKbPath, resolveWorkspaceRoot } from "./workspace.js";
|
|
67
61
|
function renderToolsDoc() {
|
|
68
62
|
const lines = [
|
|
@@ -92,9 +86,9 @@ const PROMPTS = [
|
|
|
92
86
|
"",
|
|
93
87
|
"- Encode requirements as linked facts: `req --constrains--> fact` plus `req --requires_property--> fact`.",
|
|
94
88
|
"- Reuse canonical fact IDs across requirements; shared constrained facts make contradictions detectable.",
|
|
95
|
-
"- Use
|
|
96
|
-
"- Use
|
|
97
|
-
"-
|
|
89
|
+
"- Use `kb_query` first to confirm current state before any mutation.",
|
|
90
|
+
"- Use `kb_upsert` and `kb_delete` only for intentional, traceable KB changes.",
|
|
91
|
+
"- Run `kb_check` after meaningful mutations to catch integrity issues early.",
|
|
98
92
|
"- Prefer explicit IDs and enum values to avoid invalid parameters.",
|
|
99
93
|
"- Assume every write can affect downstream traceability queries.",
|
|
100
94
|
].join("\n"),
|
|
@@ -107,15 +101,13 @@ const PROMPTS = [
|
|
|
107
101
|
"",
|
|
108
102
|
"Follow this sequence for reliable operation:",
|
|
109
103
|
"",
|
|
110
|
-
"1. **
|
|
111
|
-
"2. **
|
|
112
|
-
"3. **
|
|
113
|
-
"4. **
|
|
114
|
-
"5. **
|
|
115
|
-
"6. **Verify integrity**: Call `kb_check` after mutations.",
|
|
116
|
-
"7. **Assess impact**: Call `kb_impact`, `kb_derive`, or `kb_coverage_report` as needed.",
|
|
104
|
+
"1. **Inspect**: Call `kb_query` to confirm current state before any mutation.",
|
|
105
|
+
"2. **Model requirements as facts**: For new/updated reqs, create/reuse fact entities first, then express req semantics with `constrains` + `requires_property`.",
|
|
106
|
+
"3. **Validate intent**: If creating links, call `kb_query` for both endpoint IDs first.",
|
|
107
|
+
"4. **Mutate**: Call `kb_upsert` for create/update, or `kb_delete` for explicit removals.",
|
|
108
|
+
"5. **Verify integrity**: Call `kb_check` after mutations.",
|
|
117
109
|
"",
|
|
118
|
-
"If a tool returns empty results, do not assume failure. Re-check filters (type, id, tags, sourceFile, or
|
|
110
|
+
"If a tool returns empty results, do not assume failure. Re-check filters (type, id, tags, sourceFile, limit, or offset).",
|
|
119
111
|
].join("\n"),
|
|
120
112
|
},
|
|
121
113
|
{
|
|
@@ -128,10 +120,8 @@ const PROMPTS = [
|
|
|
128
120
|
"",
|
|
129
121
|
"- `kb_upsert` validates entity and relationship payloads against JSON Schema.",
|
|
130
122
|
"- `kb_delete` blocks deletion when dependents still reference the entity.",
|
|
131
|
-
"- `kb_branch_gc` may permanently remove stale branch KB directories when `dry_run` is `false`.",
|
|
132
123
|
"- Relationship and rule names are strict enums; unknown values fail validation.",
|
|
133
|
-
"- Branch
|
|
134
|
-
"- `kb_symbols_refresh` can rewrite the symbols manifest unless `dryRun` is enabled.",
|
|
124
|
+
"- Branch KB setup is automatic at server startup; lifecycle maintenance stays outside the public MCP tool surface.",
|
|
135
125
|
].join("\n"),
|
|
136
126
|
},
|
|
137
127
|
];
|
|
@@ -143,9 +133,8 @@ function registerDocResources() {
|
|
|
143
133
|
"",
|
|
144
134
|
"Scope:",
|
|
145
135
|
"- Entity CRUD-like operations for KB records",
|
|
146
|
-
"-
|
|
147
|
-
"-
|
|
148
|
-
"- Deterministic inference for traceability and impact analysis",
|
|
136
|
+
"- Validation of KB integrity after changes",
|
|
137
|
+
"- Automatic branch-local attachment for the active workspace",
|
|
149
138
|
"",
|
|
150
139
|
"Use this server when you need branch-local, machine-readable project memory.",
|
|
151
140
|
].join("\n");
|
|
@@ -156,7 +145,7 @@ function registerDocResources() {
|
|
|
156
145
|
"",
|
|
157
146
|
"- `-32602 INVALID_PARAMS`: Tool arguments are missing/invalid. Recover by checking enum values and required fields.",
|
|
158
147
|
"- `-32601 METHOD_NOT_FOUND`: Unknown MCP method. Recover by using supported methods (`tools/*`, `prompts/*`, `resources/*`).",
|
|
159
|
-
"- `-32000 PROLOG_QUERY_FAILED`: Prolog query failed. Recover by validating IDs, rule names, and
|
|
148
|
+
"- `-32000 PROLOG_QUERY_FAILED`: Prolog query failed. Recover by validating IDs, rule names, and branch KB availability.",
|
|
160
149
|
"- `VALIDATION_ERROR` message: `kb_upsert` payload failed schema checks. Recover by fixing required fields and enum values.",
|
|
161
150
|
"- Delete blocked by dependents: `kb_delete` detected incoming references. Recover by removing/rewiring relationships first.",
|
|
162
151
|
"- Empty results: filters may be too strict. Recover by loosening type/id/tags/source filters and retrying.",
|
|
@@ -170,20 +159,10 @@ function registerDocResources() {
|
|
|
170
159
|
"3. Reuse the same constrained fact ID across related requirements; vary property facts only when semantics differ",
|
|
171
160
|
'4. `kb_check` with `{ "rules": ["required-fields","no-dangling-refs"] }`',
|
|
172
161
|
"",
|
|
173
|
-
"## Discover requirement coverage gaps",
|
|
174
|
-
'1. `kb_query` with `{ "type": "req", "limit": 20 }`',
|
|
175
|
-
'2. `kb_coverage_report` with `{ "type": "req" }`',
|
|
176
|
-
'3. `kb_derive` with `{ "rule": "coverage_gap" }`',
|
|
177
|
-
"",
|
|
178
162
|
"## Add a requirement and link it to a test",
|
|
179
163
|
"1. `kb_query` for existing IDs to avoid collisions",
|
|
180
164
|
"2. `kb_upsert` with entity payload and `relationships` containing `verified_by`",
|
|
181
165
|
'3. `kb_check` with `{ "rules": ["required-fields","no-dangling-refs"] }`',
|
|
182
|
-
"",
|
|
183
|
-
"## Safe cleanup of stale branch KBs",
|
|
184
|
-
'1. `kb_branch_gc` with `{ "dry_run": true }`',
|
|
185
|
-
"2. Review `structuredContent.stale`",
|
|
186
|
-
'3. `kb_branch_gc` with `{ "dry_run": false }` only when deletion is intended',
|
|
187
166
|
].join("\n");
|
|
188
167
|
return [
|
|
189
168
|
{
|
|
@@ -264,6 +243,22 @@ let activeBranchName = "develop";
|
|
|
264
243
|
let isShuttingDown = false;
|
|
265
244
|
let shutdownTimeout = null;
|
|
266
245
|
const inFlightRequests = new Map();
|
|
246
|
+
function ensureBranchKbExists(workspaceRoot, branch) {
|
|
247
|
+
if (!isValidBranchName(branch)) {
|
|
248
|
+
throw new Error(`Invalid branch name: ${branch}`);
|
|
249
|
+
}
|
|
250
|
+
const branchPath = resolveKbPath(workspaceRoot, branch);
|
|
251
|
+
if (fs.existsSync(branchPath)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const templateBranch = ["develop", "main"].find((candidate) => candidate !== branch &&
|
|
255
|
+
fs.existsSync(resolveKbPath(workspaceRoot, candidate)));
|
|
256
|
+
if (!templateBranch) {
|
|
257
|
+
throw new Error(`No template branch KB found for '${branch}'. Expected '.kb/branches/develop' or '.kb/branches/main'.`);
|
|
258
|
+
}
|
|
259
|
+
// Use clean snapshot copy that excludes volatile artifacts
|
|
260
|
+
copyCleanSnapshot(resolveKbPath(workspaceRoot, templateBranch), branchPath);
|
|
261
|
+
}
|
|
267
262
|
function debugLog(...args) {
|
|
268
263
|
if (process.env.KIBI_MCP_DEBUG) {
|
|
269
264
|
console.error(...args);
|
|
@@ -315,39 +310,97 @@ async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
315
310
|
process.exit(exitCode);
|
|
316
311
|
}
|
|
317
312
|
async function ensureProlog() {
|
|
313
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
314
|
+
// Determine target branch: respect KIBI_BRANCH override or resolve from git
|
|
315
|
+
const envBranch = process.env.KIBI_BRANCH?.trim();
|
|
316
|
+
let targetBranch;
|
|
317
|
+
if (envBranch) {
|
|
318
|
+
// KIBI_BRANCH override is set - use it without re-resolving from git
|
|
319
|
+
if (!isValidBranchName(envBranch)) {
|
|
320
|
+
throw new Error(`Invalid branch name from KIBI_BRANCH: '${envBranch}'`);
|
|
321
|
+
}
|
|
322
|
+
targetBranch = envBranch;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// No override - resolve active branch from git (may change between requests)
|
|
326
|
+
const branchResult = resolveActiveBranch(workspaceRoot);
|
|
327
|
+
if ("error" in branchResult) {
|
|
328
|
+
const diagnostic = getBranchDiagnostic(undefined, branchResult.error);
|
|
329
|
+
console.error(`[KIBI-MCP] ${diagnostic}`);
|
|
330
|
+
throw new Error(`Failed to resolve active branch: ${branchResult.error}`);
|
|
331
|
+
}
|
|
332
|
+
targetBranch = branchResult.branch;
|
|
333
|
+
}
|
|
334
|
+
// Check if we need to switch branches
|
|
318
335
|
if (isInitialized && prologProcess?.isRunning()) {
|
|
336
|
+
if (targetBranch === activeBranchName) {
|
|
337
|
+
// Still on the same branch - return existing connection
|
|
338
|
+
return prologProcess;
|
|
339
|
+
}
|
|
340
|
+
// Branch changed - need to detach and re-attach
|
|
341
|
+
debugLog(`[KIBI-MCP] Branch changed: ${activeBranchName} -> ${targetBranch}`);
|
|
342
|
+
// Detach from old KB
|
|
343
|
+
const detachResult = await prologProcess.query("kb_detach");
|
|
344
|
+
if (!detachResult.success) {
|
|
345
|
+
debugLog(`[KIBI-MCP] Warning: failed to detach from old KB: ${detachResult.error || "Unknown error"}`);
|
|
346
|
+
// Continue anyway - we'll try to attach to the new KB
|
|
347
|
+
}
|
|
348
|
+
// Ensure new branch KB exists
|
|
349
|
+
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
350
|
+
const newKbPath = resolveKbPath(workspaceRoot, targetBranch);
|
|
351
|
+
// Attach to new branch KB
|
|
352
|
+
const attachResult = await prologProcess.query(`kb_attach('${newKbPath}')`);
|
|
353
|
+
if (!attachResult.success) {
|
|
354
|
+
throw new Error(`Failed to attach to new branch KB: ${attachResult.error || "Unknown error"}`);
|
|
355
|
+
}
|
|
356
|
+
activeBranchName = targetBranch;
|
|
357
|
+
debugLog(`[KIBI-MCP] Re-attached to branch: ${targetBranch}`);
|
|
358
|
+
debugLog(`[KIBI-MCP] KB path: ${newKbPath}`);
|
|
319
359
|
return prologProcess;
|
|
320
360
|
}
|
|
361
|
+
// First initialization
|
|
321
362
|
debugLog("[KIBI-MCP] Initializing Prolog process...");
|
|
322
|
-
prologProcess = new PrologProcess({ timeout:
|
|
363
|
+
prologProcess = new PrologProcess({ timeout: 120000 });
|
|
323
364
|
await prologProcess.start();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (!process.env.KIBI_BRANCH) {
|
|
365
|
+
// Startup debug: resolve which kibi-cli is being used and its version (best-effort).
|
|
366
|
+
// Gate all output under KIBI_MCP_DEBUG and write only to stderr via debugLog.
|
|
367
|
+
if (process.env.KIBI_MCP_DEBUG) {
|
|
328
368
|
try {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
369
|
+
const req = createRequire(import.meta.url);
|
|
370
|
+
try {
|
|
371
|
+
const resolved = req.resolve("kibi-cli/prolog");
|
|
372
|
+
debugLog(`[KIBI-MCP] require.resolve('kibi-cli/prolog') -> ${resolved}`);
|
|
373
|
+
}
|
|
374
|
+
catch (resolveErr) {
|
|
375
|
+
debugLog("[KIBI-MCP] require.resolve('kibi-cli/prolog') failed:", resolveErr.message);
|
|
376
|
+
}
|
|
377
|
+
// Try to read package.json for kibi-cli to get version. This may fail if
|
|
378
|
+
// the package uses exports blocking package.json access — log explicit failure.
|
|
379
|
+
try {
|
|
380
|
+
// prefer direct package.json require; createRequire makes this ESM-friendly
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
382
|
+
const pkg = req("kibi-cli/package.json");
|
|
383
|
+
if (pkg && typeof pkg.version === "string") {
|
|
384
|
+
debugLog(`[KIBI-MCP] kibi-cli version: ${pkg.version}`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
debugLog("[KIBI-MCP] kibi-cli package.json read but no version field");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (pkgErr) {
|
|
391
|
+
debugLog("[KIBI-MCP] Failed to read kibi-cli package.json (exports may restrict access):", pkgErr.message);
|
|
338
392
|
}
|
|
339
393
|
}
|
|
340
|
-
catch {
|
|
341
|
-
|
|
394
|
+
catch (err) {
|
|
395
|
+
debugLog("[KIBI-MCP] Failed to create require() for debug lookup:", err.message);
|
|
342
396
|
}
|
|
343
397
|
}
|
|
344
398
|
debugLog("[KIBI-MCP] Branch selection:");
|
|
345
399
|
debugLog(`[KIBI-MCP] KIBI_BRANCH env: ${process.env.KIBI_BRANCH || "not set"}`);
|
|
346
|
-
debugLog(`[KIBI-MCP]
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const kbPath = resolveKbPath(workspaceRoot, branch);
|
|
400
|
+
debugLog(`[KIBI-MCP] Resolved branch: ${targetBranch}`);
|
|
401
|
+
activeBranchName = targetBranch;
|
|
402
|
+
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
403
|
+
const kbPath = resolveKbPath(workspaceRoot, targetBranch);
|
|
351
404
|
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
352
405
|
if (!attachResult.success) {
|
|
353
406
|
throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
|
|
@@ -503,7 +556,7 @@ function addTool(server, name, description, inputSchema, handler) {
|
|
|
503
556
|
}
|
|
504
557
|
export async function startServer() {
|
|
505
558
|
loadDefaultEnvFile();
|
|
506
|
-
const server = new McpServer({ name: "kibi-mcp", version: "0.
|
|
559
|
+
const server = new McpServer({ name: "kibi-mcp", version: "0.2.0" });
|
|
507
560
|
attachMcpcat(server);
|
|
508
561
|
for (const prompt of PROMPTS) {
|
|
509
562
|
server.prompt(prompt.name, prompt.description, async () => ({
|
|
@@ -548,49 +601,6 @@ export async function startServer() {
|
|
|
548
601
|
const prolog = await ensureProlog();
|
|
549
602
|
return handleKbCheck(prolog, args);
|
|
550
603
|
});
|
|
551
|
-
addTool(server, "kb_branch_ensure", toolDef("kb_branch_ensure").description, toolDef("kb_branch_ensure").inputSchema, async (args) => {
|
|
552
|
-
const prolog = await ensureProlog();
|
|
553
|
-
return handleKbBranchEnsure(prolog, args);
|
|
554
|
-
});
|
|
555
|
-
addTool(server, "kb_branch_gc", toolDef("kb_branch_gc").description, toolDef("kb_branch_gc").inputSchema, async (args) => {
|
|
556
|
-
const prolog = await ensureProlog();
|
|
557
|
-
return handleKbBranchGc(prolog, args);
|
|
558
|
-
});
|
|
559
|
-
addTool(server, "kb_query_relationships", toolDef("kb_query_relationships").description, toolDef("kb_query_relationships").inputSchema, async (args) => {
|
|
560
|
-
const prolog = await ensureProlog();
|
|
561
|
-
return handleKbQueryRelationships(prolog, args);
|
|
562
|
-
});
|
|
563
|
-
addTool(server, "kb_derive", toolDef("kb_derive").description, toolDef("kb_derive").inputSchema, async (args) => {
|
|
564
|
-
const prolog = await ensureProlog();
|
|
565
|
-
return handleKbDerive(prolog, args);
|
|
566
|
-
});
|
|
567
|
-
addTool(server, "kb_impact", toolDef("kb_impact").description, toolDef("kb_impact").inputSchema, async (args) => {
|
|
568
|
-
const prolog = await ensureProlog();
|
|
569
|
-
return handleKbImpact(prolog, args);
|
|
570
|
-
});
|
|
571
|
-
addTool(server, "kb_coverage_report", toolDef("kb_coverage_report").description, toolDef("kb_coverage_report").inputSchema, async (args) => {
|
|
572
|
-
const prolog = await ensureProlog();
|
|
573
|
-
return handleKbCoverageReport(prolog, args);
|
|
574
|
-
});
|
|
575
|
-
addTool(server, "kb_symbols_refresh", toolDef("kb_symbols_refresh").description, toolDef("kb_symbols_refresh").inputSchema, async (args) => handleKbSymbolsRefresh(args));
|
|
576
|
-
addTool(server, "kb_list_entity_types", toolDef("kb_list_entity_types").description, toolDef("kb_list_entity_types").inputSchema, handleKbListEntityTypes);
|
|
577
|
-
addTool(server, "kb_list_relationship_types", toolDef("kb_list_relationship_types").description, toolDef("kb_list_relationship_types").inputSchema, handleKbListRelationshipTypes);
|
|
578
|
-
addTool(server, "kbcontext", toolDef("kbcontext").description, toolDef("kbcontext").inputSchema, async (args) => {
|
|
579
|
-
const prolog = await ensureProlog();
|
|
580
|
-
return handleKbContext(prolog, args, activeBranchName);
|
|
581
|
-
});
|
|
582
|
-
addTool(server, "get_help", toolDef("get_help").description, toolDef("get_help").inputSchema, async (args) => {
|
|
583
|
-
const topic = typeof args?.topic === "string" ? args.topic : undefined;
|
|
584
|
-
const text = getHelpText(topic);
|
|
585
|
-
return {
|
|
586
|
-
content: [{ type: "text", text }],
|
|
587
|
-
structuredContent: { topic: topic ?? "overview" },
|
|
588
|
-
};
|
|
589
|
-
});
|
|
590
|
-
addTool(server, "analyze_shared_facts", toolDef("analyze_shared_facts").description, toolDef("analyze_shared_facts").inputSchema, async (args) => {
|
|
591
|
-
const prolog = await ensureProlog();
|
|
592
|
-
return handleSuggestSharedFacts(prolog, args);
|
|
593
|
-
});
|
|
594
604
|
const transport = new StdioServerTransport();
|
|
595
605
|
transport.onerror = (error) => {
|
|
596
606
|
// Stdio transport surfaces JSON parse / schema validation failures via onerror.
|
package/dist/tools/check.js
CHANGED
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import * as path from "node:path";
|
|
46
46
|
import { parsePairList } from "./prolog-list.js";
|
|
47
|
+
function formatDiagnosticsForMcp(diagnostics) {
|
|
48
|
+
return diagnostics.map((d) => ({
|
|
49
|
+
category: d.category,
|
|
50
|
+
severity: d.severity,
|
|
51
|
+
message: d.message,
|
|
52
|
+
file: d.file,
|
|
53
|
+
suggestion: d.suggestion,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
47
56
|
/**
|
|
48
57
|
* Handle kb_check tool calls - run validation rules on the KB
|
|
49
58
|
* Reuses validation logic from CLI check command
|
|
@@ -77,7 +86,13 @@ export async function handleKbCheck(prolog, args) {
|
|
|
77
86
|
if (rulesToRun.includes("symbol-coverage")) {
|
|
78
87
|
violations.push(...(await checkSymbolCoverage(prolog)));
|
|
79
88
|
}
|
|
80
|
-
|
|
89
|
+
const diagnostics = violations.map((v) => ({
|
|
90
|
+
category: "SYNC_ERROR",
|
|
91
|
+
severity: "error",
|
|
92
|
+
message: v.description,
|
|
93
|
+
file: v.source,
|
|
94
|
+
suggestion: v.suggestion,
|
|
95
|
+
}));
|
|
81
96
|
const summary = violations.length === 0
|
|
82
97
|
? "No violations found"
|
|
83
98
|
: `${violations.length} violations found`;
|
|
@@ -91,6 +106,7 @@ export async function handleKbCheck(prolog, args) {
|
|
|
91
106
|
structuredContent: {
|
|
92
107
|
violations,
|
|
93
108
|
count: violations.length,
|
|
109
|
+
diagnostics: formatDiagnosticsForMcp(diagnostics),
|
|
94
110
|
},
|
|
95
111
|
};
|
|
96
112
|
}
|
package/dist/tools/context.js
CHANGED
|
@@ -15,18 +15,8 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
-
export async function handleKbContext(prolog, args
|
|
19
|
-
const { sourceFile
|
|
20
|
-
if (branch && activeBranch && branch !== activeBranch) {
|
|
21
|
-
return {
|
|
22
|
-
content: [
|
|
23
|
-
{
|
|
24
|
-
type: "text",
|
|
25
|
-
text: `Error: branch parameter is not supported server-side; set KIBI_BRANCH at startup or restart server on the desired branch. (Requested: ${branch}, Active: ${activeBranch})`,
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
18
|
+
export async function handleKbContext(prolog, args) {
|
|
19
|
+
const { sourceFile } = args;
|
|
30
20
|
try {
|
|
31
21
|
const safeSource = sourceFile.replace(/'/g, "\\'");
|
|
32
22
|
const entityGoal = `findall([Id,Type,Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, Type, Props)), Results)`;
|
package/dist/tools/delete.js
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
+
function escapeAtom(value) {
|
|
19
|
+
return value.replace(/'/g, "\\'");
|
|
20
|
+
}
|
|
18
21
|
/**
|
|
19
22
|
* Handle kb.delete tool calls
|
|
20
23
|
* Prevents deletion of entities with dependents (referential integrity)
|
|
@@ -30,7 +33,7 @@ export async function handleKbDelete(prolog, args) {
|
|
|
30
33
|
try {
|
|
31
34
|
for (const id of ids) {
|
|
32
35
|
// Check if entity exists
|
|
33
|
-
const checkGoal = `kb_entity('${id}', _, _)`;
|
|
36
|
+
const checkGoal = `once(kb_entity('${escapeAtom(id)}', _, _))`;
|
|
34
37
|
const checkResult = await prolog.query(checkGoal);
|
|
35
38
|
if (!checkResult.success) {
|
|
36
39
|
errors.push(`Entity ${id} does not exist`);
|
package/dist/tools/upsert.js
CHANGED
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
+
import Ajv from "ajv";
|
|
18
19
|
import entitySchema from "kibi-cli/schemas/entity";
|
|
19
20
|
import relationshipSchema from "kibi-cli/schemas/relationship";
|
|
20
|
-
import Ajv from "ajv";
|
|
21
21
|
import { refreshCoordinatesForSymbolId } from "./symbols.js";
|
|
22
|
+
function escapeAtom(value) {
|
|
23
|
+
return value.replace(/'/g, "\\'");
|
|
24
|
+
}
|
|
22
25
|
const ajv = new Ajv({ strict: false });
|
|
23
26
|
const validateEntity = ajv.compile(entitySchema);
|
|
24
27
|
const validateRelationship = ajv.compile(relationshipSchema);
|
|
@@ -80,16 +83,14 @@ export async function handleKbUpsert(prolog, args) {
|
|
|
80
83
|
const id = entity.id;
|
|
81
84
|
const type = entity.type;
|
|
82
85
|
// Check if entity exists
|
|
83
|
-
const checkGoal = `kb_entity('${id}', _, _)`;
|
|
86
|
+
const checkGoal = `once(kb_entity('${escapeAtom(id)}', _, _))`;
|
|
84
87
|
const checkResult = await prolog.query(checkGoal);
|
|
85
88
|
const isUpdate = checkResult.success;
|
|
86
89
|
// Build property list for Prolog
|
|
87
90
|
const props = buildPropertyList(entity);
|
|
88
91
|
// Assert entity (upsert)
|
|
89
92
|
if (isUpdate) {
|
|
90
|
-
//
|
|
91
|
-
const retractGoal = `kb_retract_entity('${id}')`;
|
|
92
|
-
await prolog.query(retractGoal);
|
|
93
|
+
// Update counter only. kb_assert_entity implements upsert semantics in Prolog.
|
|
93
94
|
updated++;
|
|
94
95
|
}
|
|
95
96
|
else {
|
|
@@ -108,7 +109,7 @@ export async function handleKbUpsert(prolog, args) {
|
|
|
108
109
|
const to = rel.to;
|
|
109
110
|
// Build metadata
|
|
110
111
|
const metadata = buildRelationshipMetadata(rel);
|
|
111
|
-
const relGoal = `kb_assert_relationship(${relType}, '${from}', '${to}', ${metadata})`;
|
|
112
|
+
const relGoal = `kb_assert_relationship(${relType}, '${escapeAtom(from)}', '${escapeAtom(to)}', ${metadata})`;
|
|
112
113
|
const relResult = await prolog.query(relGoal);
|
|
113
114
|
if (!relResult.success) {
|
|
114
115
|
throw new Error(`Failed to assert relationship ${relType} from ${from} to ${to}: ${relResult.error || "Unknown error"}`);
|
package/dist/tools-config.js
CHANGED
|
@@ -234,215 +234,19 @@ export const TOOLS = [
|
|
|
234
234
|
properties: {
|
|
235
235
|
rules: {
|
|
236
236
|
type: "array",
|
|
237
|
-
items: {
|
|
237
|
+
items: {
|
|
238
|
+
type: "string",
|
|
239
|
+
enum: [
|
|
240
|
+
"must-priority-coverage",
|
|
241
|
+
"no-dangling-refs",
|
|
242
|
+
"no-cycles",
|
|
243
|
+
"required-fields",
|
|
244
|
+
"symbol-coverage",
|
|
245
|
+
],
|
|
246
|
+
},
|
|
238
247
|
description: "Optional rule subset. Allowed: must-priority-coverage, no-dangling-refs, no-cycles, required-fields, symbol-coverage. If omitted, server runs all.",
|
|
239
248
|
},
|
|
240
249
|
},
|
|
241
250
|
},
|
|
242
251
|
},
|
|
243
|
-
{
|
|
244
|
-
name: "kb_branch_ensure",
|
|
245
|
-
description: "Ensure a branch KB exists, creating it from develop when missing. Use when targeting non-develop branches. Do not use to switch git branches. Side effects: creates .kb/branches/<branch>.",
|
|
246
|
-
inputSchema: {
|
|
247
|
-
type: "object",
|
|
248
|
-
required: ["branch"],
|
|
249
|
-
properties: {
|
|
250
|
-
branch: {
|
|
251
|
-
type: "string",
|
|
252
|
-
description: "Required git branch name. Example: 'feature/auth-hardening'. Path traversal patterns are rejected.",
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
name: "kb_branch_gc",
|
|
259
|
-
description: "Find or delete stale branch KB directories not present in git. Use for repository hygiene. Do not use if you need historical branch KBs. Side effects: can delete branch KB folders when dry_run is false.",
|
|
260
|
-
inputSchema: {
|
|
261
|
-
type: "object",
|
|
262
|
-
properties: {
|
|
263
|
-
dry_run: {
|
|
264
|
-
type: "boolean",
|
|
265
|
-
default: true,
|
|
266
|
-
description: "Optional safety flag. true = report only; false = delete stale branch KBs. Default: true.",
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: "kb_query_relationships",
|
|
273
|
-
description: "Read relationship edges with optional from/to/type filters. Use for traceability traversal. Do not use to create links. No mutation side effects.",
|
|
274
|
-
inputSchema: {
|
|
275
|
-
type: "object",
|
|
276
|
-
properties: {
|
|
277
|
-
from: {
|
|
278
|
-
type: "string",
|
|
279
|
-
description: "Optional source entity ID filter. Example: 'REQ-001'.",
|
|
280
|
-
},
|
|
281
|
-
to: {
|
|
282
|
-
type: "string",
|
|
283
|
-
description: "Optional target entity ID filter. Example: 'TEST-010'.",
|
|
284
|
-
},
|
|
285
|
-
type: {
|
|
286
|
-
type: "string",
|
|
287
|
-
enum: [
|
|
288
|
-
"depends_on",
|
|
289
|
-
"specified_by",
|
|
290
|
-
"verified_by",
|
|
291
|
-
"validates",
|
|
292
|
-
"implements",
|
|
293
|
-
"covered_by",
|
|
294
|
-
"constrained_by",
|
|
295
|
-
"constrains",
|
|
296
|
-
"requires_property",
|
|
297
|
-
"guards",
|
|
298
|
-
"publishes",
|
|
299
|
-
"consumes",
|
|
300
|
-
"supersedes",
|
|
301
|
-
"relates_to",
|
|
302
|
-
],
|
|
303
|
-
description: "Optional relationship type filter. Allowed enum values only. Example: 'implements'.",
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: "kb_derive",
|
|
310
|
-
description: "Run deterministic inference predicates and return rows. Use for impact, coverage, and consistency analysis. Do not use for entity CRUD. No mutation side effects.",
|
|
311
|
-
inputSchema: {
|
|
312
|
-
type: "object",
|
|
313
|
-
required: ["rule"],
|
|
314
|
-
properties: {
|
|
315
|
-
rule: {
|
|
316
|
-
type: "string",
|
|
317
|
-
enum: [
|
|
318
|
-
"transitively_implements",
|
|
319
|
-
"transitively_depends",
|
|
320
|
-
"impacted_by_change",
|
|
321
|
-
"affected_symbols",
|
|
322
|
-
"coverage_gap",
|
|
323
|
-
"untested_symbols",
|
|
324
|
-
"stale",
|
|
325
|
-
"orphaned",
|
|
326
|
-
"conflicting",
|
|
327
|
-
"deprecated_still_used",
|
|
328
|
-
"current_adr",
|
|
329
|
-
"adr_chain",
|
|
330
|
-
"superseded_by",
|
|
331
|
-
"domain_contradictions",
|
|
332
|
-
],
|
|
333
|
-
description: "Required inference rule name. Allowed values are the enum options. Example: 'coverage_gap'.",
|
|
334
|
-
},
|
|
335
|
-
params: {
|
|
336
|
-
type: "object",
|
|
337
|
-
description: "Optional rule-specific parameters. Example: { changed: 'REQ-001' } for impacted_by_change.",
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: "kb_impact",
|
|
344
|
-
description: "Return entities impacted by a changed entity ID. Use for quick change blast radius checks. Do not use for general querying. No mutation side effects.",
|
|
345
|
-
inputSchema: {
|
|
346
|
-
type: "object",
|
|
347
|
-
required: ["entity"],
|
|
348
|
-
properties: {
|
|
349
|
-
entity: {
|
|
350
|
-
type: "string",
|
|
351
|
-
description: "Required changed entity ID. Example: 'REQ-001'.",
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
name: "kb_coverage_report",
|
|
358
|
-
description: "Compute aggregate traceability coverage for requirements and/or symbols. Use for health snapshots. Do not use for raw entity dumps. No mutation side effects.",
|
|
359
|
-
inputSchema: {
|
|
360
|
-
type: "object",
|
|
361
|
-
properties: {
|
|
362
|
-
type: {
|
|
363
|
-
type: "string",
|
|
364
|
-
enum: ["req", "symbol"],
|
|
365
|
-
description: "Optional focus scope: 'req' or 'symbol'. Omit to include both.",
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
name: "kb_symbols_refresh",
|
|
372
|
-
description: "Refresh generated symbol coordinates in the symbols manifest. Use after refactors that move symbols. Do not use for semantic edits. Side effects: may rewrite symbols.yaml unless dryRun is true.",
|
|
373
|
-
inputSchema: {
|
|
374
|
-
type: "object",
|
|
375
|
-
properties: {
|
|
376
|
-
dryRun: {
|
|
377
|
-
type: "boolean",
|
|
378
|
-
default: false,
|
|
379
|
-
description: "Optional preview mode. true = report only, false = apply file updates. Default: false.",
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: "kb_list_entity_types",
|
|
386
|
-
description: "List supported entity type names. Use when building valid tool arguments. Do not use for entity data retrieval. No mutation side effects.",
|
|
387
|
-
inputSchema: { type: "object", properties: {} },
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
name: "kb_list_relationship_types",
|
|
391
|
-
description: "List supported relationship type names. Use before asserting or filtering relationships. Do not use for graph traversal. No mutation side effects.",
|
|
392
|
-
inputSchema: { type: "object", properties: {} },
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
name: "kbcontext",
|
|
396
|
-
description: "Return KB entities linked to a source file plus first-hop relationships. Use for file-centric traceability. Do not use for cross-repo search. No mutation side effects.",
|
|
397
|
-
inputSchema: {
|
|
398
|
-
type: "object",
|
|
399
|
-
required: ["sourceFile"],
|
|
400
|
-
properties: {
|
|
401
|
-
sourceFile: {
|
|
402
|
-
type: "string",
|
|
403
|
-
description: "Required source path substring. Example: 'src/auth/login.ts'.",
|
|
404
|
-
},
|
|
405
|
-
branch: {
|
|
406
|
-
type: "string",
|
|
407
|
-
description: "Optional branch hint for clients. Must match the server's active branch or will return an error.",
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
name: "get_help",
|
|
414
|
-
description: "Returns documentation for this MCP server. Call this first if you are unsure how to proceed or which tool to use. Available topics: overview, tools, workflow, constraints, examples, errors.",
|
|
415
|
-
inputSchema: {
|
|
416
|
-
type: "object",
|
|
417
|
-
properties: {
|
|
418
|
-
topic: {
|
|
419
|
-
type: "string",
|
|
420
|
-
enum: [
|
|
421
|
-
"overview",
|
|
422
|
-
"tools",
|
|
423
|
-
"workflow",
|
|
424
|
-
"constraints",
|
|
425
|
-
"examples",
|
|
426
|
-
"errors",
|
|
427
|
-
"branching",
|
|
428
|
-
],
|
|
429
|
-
description: "Optional documentation section. Omit to return overview. Example: 'workflow'.",
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
name: "analyze_shared_facts",
|
|
436
|
-
description: "Analyze requirements and suggest shared domain facts for extraction. LLMs call this to identify missed semantic opportunities before upserting. Lightweight heuristic: finds overlapping capitalized terms and repeated phrases across requirements.",
|
|
437
|
-
inputSchema: {
|
|
438
|
-
type: "object",
|
|
439
|
-
properties: {
|
|
440
|
-
min_frequency: {
|
|
441
|
-
type: "number",
|
|
442
|
-
default: 2,
|
|
443
|
-
description: "Minimum frequency threshold for shared concepts. Default: 2. Example: 3 to only show concepts mentioned in 3+ requirements.",
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
252
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,7 +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-
|
|
12
|
+
"kibi-cli": "^0.2.0",
|
|
13
|
+
"kibi-core": "^0.1.6",
|
|
13
14
|
"mcpcat": "^0.1.12",
|
|
14
15
|
"ts-morph": "^23.0.0",
|
|
15
16
|
"zod": "^4.3.6"
|
|
@@ -18,13 +19,13 @@
|
|
|
18
19
|
"description": "Model Context Protocol server for Kibi knowledge base",
|
|
19
20
|
"main": "./dist/server.js",
|
|
20
21
|
"bin": {
|
|
21
|
-
"kibi-mcp": "
|
|
22
|
+
"kibi-mcp": "bin/kibi-mcp"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
25
|
"test": "bun test",
|
|
25
26
|
"dev": "bun run bin/kibi-mcp",
|
|
26
27
|
"build": "tsc -p tsconfig.json",
|
|
27
|
-
"
|
|
28
|
+
"prepack": "npm run build"
|
|
28
29
|
},
|
|
29
30
|
"files": ["dist", "bin"],
|
|
30
31
|
"engines": {
|
|
@@ -32,10 +33,8 @@
|
|
|
32
33
|
"bun": ">=1.0"
|
|
33
34
|
},
|
|
34
35
|
"exports": {
|
|
35
|
-
".": "./dist/server.js"
|
|
36
|
-
"./src/*": "./src/*.ts"
|
|
36
|
+
".": "./dist/server.js"
|
|
37
37
|
},
|
|
38
|
-
|
|
39
38
|
"devDependencies": {
|
|
40
39
|
"@types/bun": "latest",
|
|
41
40
|
"@types/node": "latest"
|