kibi-mcp 0.22.0 → 2.0.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/bin/kibi-mcp +12 -11
- package/dist/diagnostics.js +68 -8
- package/dist/env.js +5 -1
- package/dist/runtime/mcp-runtime.js +39 -3
- package/dist/server/docs.js +31 -42
- package/dist/server/icons.js +27 -0
- package/dist/server/jobs.js +107 -0
- package/dist/server/json-schema-to-zod.js +33 -9
- package/dist/server/session.js +129 -133
- package/dist/server/tool-registration.js +46 -9
- package/dist/server/tools-runtime.js +10 -10
- package/dist/server/tools.js +143 -7
- package/dist/server.js +16 -1
- package/dist/startup-resolution.js +15 -17
- package/dist/tools/check-impact.js +1 -1
- package/dist/tools/check.js +1 -1
- package/dist/tools/core-module.js +19 -8
- package/dist/tools/coverage.js +1 -1
- package/dist/tools/delete.js +1 -1
- package/dist/tools/discovery-adapter.js +5 -0
- package/dist/tools/entity-query.js +1 -1
- package/dist/tools/find-gaps.js +9 -1
- package/dist/tools/graph.js +9 -1
- package/dist/tools/model-requirement.js +2 -2
- package/dist/tools/mutation-context.js +15 -9
- package/dist/tools/plan-bootstrap.js +21 -0
- package/dist/tools/prolog-list.js +1 -1
- package/dist/tools/query.js +1 -1
- package/dist/tools/relationship-validation.js +1 -1
- package/dist/tools/search.js +1 -1
- package/dist/tools/semantic-advisor.js +1 -1
- package/dist/tools/skills.js +2 -2
- package/dist/tools/sparql.js +1 -1
- package/dist/tools/status.js +1 -1
- package/dist/tools/suggest-predicates.js +1 -1
- package/dist/tools/symbols.js +310 -68
- package/dist/tools/upsert.js +3 -3
- package/dist/tools/validate-upsert.js +1 -1
- package/dist/tools-config.js +77 -3
- package/dist/workspace.js +10 -8
- package/package.json +15 -9
- package/dist/tools/autopilot-candidates.js +0 -554
- package/dist/tools/autopilot-discovery.js +0 -898
- package/dist/tools/autopilot-generate.js +0 -47
- package/dist/tools/autopilot-test-compat.js +0 -88
- package/dist/tools/autopilot-test-parsers.js +0 -120
- package/dist/tools/check-config.js +0 -67
package/bin/kibi-mcp
CHANGED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
const args = globalThis.Bun?.argv?.slice(2) ?? process.argv.slice(2);
|
|
20
20
|
|
|
21
21
|
async function importStartupResolution() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// Published packages contain the compiled runtime only. Do not replace a
|
|
23
|
+
// real dependency/import failure with a source-tree fallback: doing so
|
|
24
|
+
// hides package contract errors and makes startup depend on an unpackaged
|
|
25
|
+
// TypeScript tree.
|
|
26
|
+
return await import("../dist/startup-resolution.js");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function serverEntrypointUrl() {
|
|
@@ -32,7 +32,10 @@ async function serverEntrypointUrl() {
|
|
|
32
32
|
if (existsSync(distUrl)) {
|
|
33
33
|
return distUrl.href;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[KIBI-MCP] Missing compiled server entrypoint: ${distUrl.pathname}. ` +
|
|
37
|
+
"Run the package build before launching kibi-mcp.",
|
|
38
|
+
);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
async function buildResolution() {
|
|
@@ -64,11 +67,9 @@ async function importServer(entrypoint) {
|
|
|
64
67
|
const { pathToFileURL } = await import("node:url");
|
|
65
68
|
return await import(pathToFileURL(entrypoint).href);
|
|
66
69
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return await import("../src/server.ts");
|
|
71
|
-
}
|
|
70
|
+
// Preserve the original import error. In particular, an incompatible
|
|
71
|
+
// kibi-cli export must not be replaced with a misleading source fallback.
|
|
72
|
+
return await import("../dist/server.js");
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
function printHelp() {
|
package/dist/diagnostics.js
CHANGED
|
@@ -140,12 +140,50 @@ function appendMutationFields(fields, args) {
|
|
|
140
140
|
fields.mutation_target = `${type}:${id}`;
|
|
141
141
|
}
|
|
142
142
|
const RECEIPT_GAP_CODES = new Set([
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
143
|
+
"missing_proof_receipt",
|
|
144
|
+
"stale_proof_receipt",
|
|
145
|
+
"failed_proof_receipt",
|
|
146
|
+
"invalid_proof_receipt",
|
|
147
|
+
"proof_contract_mismatch",
|
|
148
|
+
"proof_snapshot_unavailable",
|
|
148
149
|
]);
|
|
150
|
+
const MAX_COVERAGE_RECEIPT_GAPS = 50;
|
|
151
|
+
function coverageReceiptGapDetails(rows) {
|
|
152
|
+
const details = rows
|
|
153
|
+
.flatMap((row) => {
|
|
154
|
+
const codes = [...new Set(stringArray(row.proofGaps))]
|
|
155
|
+
.filter((code) => RECEIPT_GAP_CODES.has(code))
|
|
156
|
+
.sort();
|
|
157
|
+
if (codes.length === 0)
|
|
158
|
+
return [];
|
|
159
|
+
const stages = isRecord(row.proofStages) ? row.proofStages : undefined;
|
|
160
|
+
const passingE2e = isRecord(stages?.passingE2e)
|
|
161
|
+
? stages.passingE2e
|
|
162
|
+
: undefined;
|
|
163
|
+
const testIds = [
|
|
164
|
+
"missingReceiptTests",
|
|
165
|
+
"staleReceiptTests",
|
|
166
|
+
"failedReceiptTests",
|
|
167
|
+
"invalidReceiptTests",
|
|
168
|
+
"contractMismatchReceiptTests",
|
|
169
|
+
"snapshotUnavailableTests",
|
|
170
|
+
].flatMap((key) => stringArray(passingE2e?.[key]));
|
|
171
|
+
const requirementId = typeof row.id === "string" ? row.id : "unknown";
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
requirementId,
|
|
175
|
+
testIds: [...new Set(testIds)].sort(),
|
|
176
|
+
codes,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
})
|
|
180
|
+
.sort((left, right) => left.requirementId.localeCompare(right.requirementId));
|
|
181
|
+
return {
|
|
182
|
+
details: details.slice(0, MAX_COVERAGE_RECEIPT_GAPS),
|
|
183
|
+
total: details.length,
|
|
184
|
+
truncated: details.length > MAX_COVERAGE_RECEIPT_GAPS,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
149
187
|
function appendCoverageFields(fields, args, structuredContent) {
|
|
150
188
|
const rows = Array.isArray(structuredContent.rows)
|
|
151
189
|
? structuredContent.rows.filter(isRecord)
|
|
@@ -166,14 +204,18 @@ function appendCoverageFields(fields, args, structuredContent) {
|
|
|
166
204
|
fields.coverage_proof_gap_count = gapCodes.length;
|
|
167
205
|
fields.coverage_gap_codes = uniqueGapCodes;
|
|
168
206
|
fields.coverage_receipt_gap_count = gapCodes.filter((gap) => RECEIPT_GAP_CODES.has(gap)).length;
|
|
207
|
+
const receiptGaps = coverageReceiptGapDetails(rows);
|
|
208
|
+
fields.coverage_receipt_gaps = receiptGaps.details;
|
|
209
|
+
fields.coverage_receipt_gap_total = receiptGaps.total;
|
|
210
|
+
fields.coverage_receipt_gaps_truncated = receiptGaps.truncated;
|
|
169
211
|
if (typeof scope?.complete === "boolean") {
|
|
170
212
|
fields.coverage_scope_complete = scope.complete;
|
|
171
213
|
}
|
|
172
214
|
const metadata = isRecord(structuredContent.meta)
|
|
173
215
|
? structuredContent.meta
|
|
174
216
|
: undefined;
|
|
175
|
-
if (typeof metadata?.
|
|
176
|
-
fields.
|
|
217
|
+
if (typeof metadata?.proofSnapshot === "string") {
|
|
218
|
+
fields.coverage_proof_snapshot = metadata.proofSnapshot;
|
|
177
219
|
}
|
|
178
220
|
}
|
|
179
221
|
function appendPredicateSuggestionFields(fields, structuredContent) {
|
|
@@ -234,7 +276,25 @@ export function deriveDiagnosticFields(toolName, args, telemetry, result) {
|
|
|
234
276
|
fields.actor_id = telemetry.actor_id;
|
|
235
277
|
}
|
|
236
278
|
}
|
|
237
|
-
const
|
|
279
|
+
const envelope = structuredContentFrom(result);
|
|
280
|
+
const structuredContent = envelope?.kibiProtocol === 1 && isRecord(envelope.data)
|
|
281
|
+
? envelope.data
|
|
282
|
+
: envelope;
|
|
283
|
+
if (envelope?.kibiProtocol === 1) {
|
|
284
|
+
fields.protocol_version = envelope.kibiProtocol;
|
|
285
|
+
fields.result_version = envelope.resultVersion ?? null;
|
|
286
|
+
fields.result_status = envelope.status ?? null;
|
|
287
|
+
fields.effect_failures = Array.isArray(envelope.effects)
|
|
288
|
+
? envelope.effects.filter((effect) => effect !== null &&
|
|
289
|
+
typeof effect === "object" &&
|
|
290
|
+
!Array.isArray(effect) &&
|
|
291
|
+
effect.status === "failed")
|
|
292
|
+
: [];
|
|
293
|
+
fields.followed_next_actions = Array.isArray(telemetry?.followed_next_actions)
|
|
294
|
+
? telemetry.followed_next_actions
|
|
295
|
+
: [];
|
|
296
|
+
fields.unsafe_original_retry = telemetry?.unsafe_original_retry === true;
|
|
297
|
+
}
|
|
238
298
|
if (toolName === "kb_query" || toolName === "kb_search") {
|
|
239
299
|
const resultCount = Number(structuredContent?.count ?? 0);
|
|
240
300
|
fields.result_count = resultCount;
|
package/dist/env.js
CHANGED
|
@@ -36,7 +36,11 @@ export function isMcpDebugEnabled() {
|
|
|
36
36
|
}
|
|
37
37
|
export function getBranchOverride() {
|
|
38
38
|
// implements REQ-002
|
|
39
|
-
|
|
39
|
+
// Branch names are identities, not display labels. Preserve the value
|
|
40
|
+
// exactly so the Git-ref validator can reject whitespace or other invalid
|
|
41
|
+
// input instead of silently attaching a different branch.
|
|
42
|
+
const value = getEnvValue("KIBI_BRANCH");
|
|
43
|
+
return value === undefined || value.length === 0 ? undefined : value;
|
|
40
44
|
}
|
|
41
45
|
export function getKbPlPathOverride() {
|
|
42
46
|
// implements REQ-002
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { resolveBranchAttachment } from "kibi-runtime";
|
|
2
|
+
export function attachedContextWithProlog(withAttachment, prolog) {
|
|
3
|
+
return { ...withAttachment, prolog };
|
|
4
|
+
}
|
|
1
5
|
// implements REQ-kibi-operation-interface-parity
|
|
2
6
|
export function createMcpRuntime(session) {
|
|
3
7
|
const sessionPrologs = new WeakMap();
|
|
@@ -11,18 +15,50 @@ export function createMcpRuntime(session) {
|
|
|
11
15
|
signal: options.signal ?? session.signal ?? new AbortController().signal,
|
|
12
16
|
clock: options.clock ?? session.clock ?? (() => new Date()),
|
|
13
17
|
...(fs ? { fs } : {}),
|
|
18
|
+
...(fs ? { sourceFirst: true } : {}),
|
|
14
19
|
...(git ? { git } : {}),
|
|
15
20
|
...(net ? { net } : {}),
|
|
16
21
|
};
|
|
22
|
+
const attachment = resolveBranchAttachment(context.workspaceRoot);
|
|
23
|
+
if (!("error" in attachment) && attachment.migrationRequired) {
|
|
24
|
+
console.warn(`[KIBI-MCP] Legacy branch attachment: Git '${attachment.gitBranch}' is reading KB '${attachment.kbBranch}'. Run 'kibi branch migrate --from ${attachment.kbBranch} --to ${attachment.gitBranch} --apply'; writes are blocked until then.`);
|
|
25
|
+
}
|
|
26
|
+
const withAttachment = "error" in attachment
|
|
27
|
+
? context
|
|
28
|
+
: { ...context, branchAttachment: attachment };
|
|
17
29
|
if (!spec.requiresProlog) {
|
|
18
|
-
|
|
30
|
+
// Preserve explicit host/test injection without asking the shared MCP
|
|
31
|
+
// session to initialise a store merely to report its condition.
|
|
32
|
+
const providedProlog = options.prolog;
|
|
33
|
+
let lazyProlog;
|
|
34
|
+
const lazyContext = {};
|
|
35
|
+
const ensureProlog = async () => {
|
|
36
|
+
if (providedProlog !== undefined)
|
|
37
|
+
return providedProlog;
|
|
38
|
+
if (lazyProlog !== undefined)
|
|
39
|
+
return session.adaptProlog(lazyProlog);
|
|
40
|
+
lazyProlog = await session.ensureProlog();
|
|
41
|
+
if (lazyContext.current !== undefined)
|
|
42
|
+
sessionPrologs.set(lazyContext.current, lazyProlog);
|
|
43
|
+
return session.adaptProlog(lazyProlog);
|
|
44
|
+
};
|
|
45
|
+
const operationContext = {
|
|
46
|
+
...withAttachment,
|
|
47
|
+
...(options.prolog ? { prolog: options.prolog } : {}),
|
|
48
|
+
ensureProlog,
|
|
49
|
+
};
|
|
50
|
+
lazyContext.current = operationContext;
|
|
51
|
+
return operationContext;
|
|
52
|
+
}
|
|
53
|
+
if (!("error" in attachment) && attachment.migrationRequired) {
|
|
54
|
+
throw new Error(`Legacy branch attachment requires explicit migration before '${spec.name}'; run kibi branch migrate --from ${attachment.kbBranch} --to ${attachment.gitBranch} --apply`);
|
|
19
55
|
}
|
|
20
56
|
if (options.prolog) {
|
|
21
|
-
return
|
|
57
|
+
return attachedContextWithProlog(withAttachment, options.prolog);
|
|
22
58
|
}
|
|
23
59
|
const sessionProlog = await session.ensureProlog();
|
|
24
60
|
const operationContext = {
|
|
25
|
-
...
|
|
61
|
+
...withAttachment,
|
|
26
62
|
prolog: session.adaptProlog(sessionProlog),
|
|
27
63
|
};
|
|
28
64
|
sessionPrologs.set(operationContext, sessionProlog);
|
package/dist/server/docs.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 { TOOLS } from "../tools-config.js";
|
|
19
|
+
import { KIBI_ICONS } from "./icons.js";
|
|
19
20
|
const ACTIVE_TOOLS = TOOLS;
|
|
20
21
|
function renderToolsDoc() {
|
|
21
22
|
const lines = [
|
|
@@ -41,58 +42,32 @@ function renderToolsDoc() {
|
|
|
41
42
|
export const PROMPTS = [
|
|
42
43
|
// implements REQ-002, REQ-013, REQ-mcp-search-discovery
|
|
43
44
|
{
|
|
44
|
-
name: "
|
|
45
|
-
|
|
45
|
+
name: "kibi-bootstrap",
|
|
46
|
+
title: "Bootstrap Kibi knowledge",
|
|
47
|
+
description: "Bootstrap Kibi knowledge for an existing repository through the canonical planner and apply operation.",
|
|
46
48
|
text: [
|
|
47
|
-
"# Kibi
|
|
49
|
+
"# Kibi Bootstrap",
|
|
48
50
|
"",
|
|
49
|
-
"
|
|
51
|
+
"Route an explicit bootstrap request to the canonical `kibi-bootstrap` skill and planner.",
|
|
50
52
|
"",
|
|
51
|
-
"##
|
|
53
|
+
"## Route",
|
|
52
54
|
"",
|
|
53
|
-
"
|
|
54
|
-
"1. **Project Summary**: What is the core purpose of this project?",
|
|
55
|
-
"2. **Source of Truth**: Where is the primary documentation (canonical requirements, ADRs)?",
|
|
56
|
-
"3. **Priority Root**: In a monorepo, which package should be prioritized?",
|
|
57
|
-
"4. **Verification Anchors**: Where are the primary tests or verification configs located?",
|
|
55
|
+
"Inspect `kb_status.bootstrap` and follow its typed `nextAction`. If infrastructure is missing, run `kibi init` before planning.",
|
|
58
56
|
"",
|
|
59
|
-
"##
|
|
57
|
+
"## Preview and Approval",
|
|
60
58
|
"",
|
|
61
|
-
"Call `
|
|
59
|
+
"Call `kb_plan_bootstrap` read-only. Ask only the bounded questions returned when its status is `needs_context`; do not invent a questionnaire.",
|
|
62
60
|
"",
|
|
63
|
-
"
|
|
64
|
-
"- `promptBlock`: review text that must be surfaced before writes",
|
|
65
|
-
"- `recommendedActions`: agent-facing next steps, including any REQ/SCEN/TEST authoring routed for manual handling",
|
|
66
|
-
"- `declaredContext`: the user-provided bootstrap context",
|
|
67
|
-
"- `confidence`: confidence summary for the generated output",
|
|
68
|
-
"- `bootstrapMode`: current KB state (e.g., `root_uninitialized`)",
|
|
69
|
-
"- `candidates`: synthesized entities grounded in declared context and source evidence",
|
|
70
|
-
"- `applyPlan`: exact sequential `kb_upsert` payloads for approved candidates",
|
|
71
|
-
"- `discoverySummary`: source-backed discovery notes",
|
|
61
|
+
"Show the exact returned `structuredContent.plan`, its actions, and canonical hash. Get explicit user approval, then pass that plan object unchanged to `kb_apply_plan`.",
|
|
72
62
|
"",
|
|
73
|
-
"##
|
|
63
|
+
"## Verify and repair",
|
|
74
64
|
"",
|
|
75
|
-
"
|
|
76
|
-
"",
|
|
77
|
-
"## Step 4: Apply Candidates",
|
|
78
|
-
"",
|
|
79
|
-
"Apply candidates sequentially using `kb_upsert`.",
|
|
80
|
-
"1. Execute `structuredContent.applyPlan` sequentially in listed order.",
|
|
81
|
-
"2. Confirm success of each `kb_upsert` before moving to the next.",
|
|
82
|
-
"3. Run `kb_check` after the batch to verify KB integrity.",
|
|
83
|
-
"",
|
|
84
|
-
"## Rules",
|
|
85
|
-
"- Never apply bootstrap writes without user-facing preview and explicit approval.",
|
|
86
|
-
"- `kb_autopilot_generate` is strictly read-only; synthesis is the backend, not the actor.",
|
|
87
|
-
"- MCP tools and the trusted project-local CLI are peer surfaces; choose by what is visible and approved here.",
|
|
88
|
-
"- If Kibi MCP tools are visible and approved, use MCP.",
|
|
89
|
-
"- Otherwise, in a trusted workspace, use the project-local CLI's dedicated JSON routes with `--input <file|->`.",
|
|
90
|
-
"- 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.",
|
|
91
|
-
"- Do not read or edit `.kb/` files directly. Query before mutate. Run `kb_upsert` sequentially. Run `kb_check` before completion.",
|
|
65
|
+
"Inspect the typed `kb_apply_plan` result, follow any `nextActions` for recovery, and finish with `kb_check` and `kb_status`. Never read or edit `.kb` directly or reconstruct a plan.",
|
|
92
66
|
].join("\n"),
|
|
93
67
|
},
|
|
94
68
|
{
|
|
95
69
|
name: "kibi_overview",
|
|
70
|
+
title: "Kibi usage overview",
|
|
96
71
|
description: "High-level model for using kibi-mcp safely and effectively.",
|
|
97
72
|
text: [
|
|
98
73
|
"# kibi-mcp Overview",
|
|
@@ -113,7 +88,7 @@ export const PROMPTS = [
|
|
|
113
88
|
"Core modeling principles:",
|
|
114
89
|
"- Kibi has eight entity types: common authoring (req, scenario, test, fact) and supporting/system (adr, flag, event, symbol).",
|
|
115
90
|
"- Encode requirements as linked facts: `req --constrains--> fact` plus `req --requires_property--> fact`.",
|
|
116
|
-
"- High-confidence `kb_model_requirement` output is deterministic; `/
|
|
91
|
+
"- High-confidence `kb_model_requirement` output is deterministic; `/kibi-bootstrap` bootstrap writes still require preview and explicit approval.",
|
|
117
92
|
"- Low-confidence claims (< 0.7) are downgraded to `observation` facts to prevent false-positive contradictions.",
|
|
118
93
|
"- Only strict domain facts participate in contradiction inference; observation and meta facts are non-blocking notes.",
|
|
119
94
|
"- v1 contradictions are limited to exact-value, boolean/enum, numeric range, and polarity conflicts.",
|
|
@@ -127,6 +102,7 @@ export const PROMPTS = [
|
|
|
127
102
|
},
|
|
128
103
|
{
|
|
129
104
|
name: "kibi_workflow",
|
|
105
|
+
title: "Kibi workflow steps",
|
|
130
106
|
description: "Step-by-step call order for discovery, mutation, and verification.",
|
|
131
107
|
text: [
|
|
132
108
|
"# kibi-mcp Workflow",
|
|
@@ -149,6 +125,7 @@ export const PROMPTS = [
|
|
|
149
125
|
},
|
|
150
126
|
{
|
|
151
127
|
name: "kibi_constraints",
|
|
128
|
+
title: "Kibi constraints",
|
|
152
129
|
description: "Operational limits, validation rules, and mutation gotchas.",
|
|
153
130
|
text: [
|
|
154
131
|
"# kibi-mcp Constraints",
|
|
@@ -236,6 +213,7 @@ function registerDocResources() {
|
|
|
236
213
|
{
|
|
237
214
|
uri: "kibi://docs/overview",
|
|
238
215
|
name: "kibi docs overview",
|
|
216
|
+
title: "Kibi server overview",
|
|
239
217
|
description: "Full server description, purpose, and scope.",
|
|
240
218
|
mimeType: "text/markdown",
|
|
241
219
|
text: overview,
|
|
@@ -243,6 +221,7 @@ function registerDocResources() {
|
|
|
243
221
|
{
|
|
244
222
|
uri: "kibi://docs/tools",
|
|
245
223
|
name: "kibi docs tools",
|
|
224
|
+
title: "Kibi tools reference",
|
|
246
225
|
description: "Available tools with summaries and required parameters.",
|
|
247
226
|
mimeType: "text/markdown",
|
|
248
227
|
text: renderToolsDoc(),
|
|
@@ -250,6 +229,7 @@ function registerDocResources() {
|
|
|
250
229
|
{
|
|
251
230
|
uri: "kibi://docs/errors",
|
|
252
231
|
name: "kibi docs errors",
|
|
232
|
+
title: "Kibi error guide",
|
|
253
233
|
description: "Common error modes and suggested recovery actions.",
|
|
254
234
|
mimeType: "text/markdown",
|
|
255
235
|
text: errors,
|
|
@@ -257,6 +237,7 @@ function registerDocResources() {
|
|
|
257
237
|
{
|
|
258
238
|
uri: "kibi://docs/examples",
|
|
259
239
|
name: "kibi docs examples",
|
|
240
|
+
title: "Kibi usage examples",
|
|
260
241
|
description: "Concrete tool call sequences for common tasks.",
|
|
261
242
|
mimeType: "text/markdown",
|
|
262
243
|
text: examples,
|
|
@@ -266,7 +247,10 @@ function registerDocResources() {
|
|
|
266
247
|
export const DOC_RESOURCES = registerDocResources();
|
|
267
248
|
export function setupDocsAndPrompts(server) {
|
|
268
249
|
for (const prompt of PROMPTS) {
|
|
269
|
-
server.
|
|
250
|
+
server.registerPrompt(prompt.name, {
|
|
251
|
+
title: prompt.title,
|
|
252
|
+
description: prompt.description,
|
|
253
|
+
}, async () => ({
|
|
270
254
|
messages: [
|
|
271
255
|
{
|
|
272
256
|
role: "user",
|
|
@@ -276,7 +260,12 @@ export function setupDocsAndPrompts(server) {
|
|
|
276
260
|
}));
|
|
277
261
|
}
|
|
278
262
|
for (const resource of DOC_RESOURCES) {
|
|
279
|
-
server.
|
|
263
|
+
server.registerResource(resource.name, resource.uri, {
|
|
264
|
+
title: resource.title,
|
|
265
|
+
description: resource.description,
|
|
266
|
+
mimeType: resource.mimeType,
|
|
267
|
+
icons: KIBI_ICONS,
|
|
268
|
+
}, async () => ({
|
|
280
269
|
contents: [
|
|
281
270
|
{
|
|
282
271
|
uri: resource.uri,
|
|
@@ -0,0 +1,27 @@
|
|
|
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/>.
|
|
17
|
+
*/
|
|
18
|
+
const KIBI_LOGO_MARK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 308 309"><rect width="308" height="309" rx="64" fill="#1D1E23"/><ellipse cx="113.3" cy="150.9" rx="51.3" ry="51.5" fill="#A2D3F4"/><path d="M228.1 197.8L227.6 165.5H156.5L95.4 165.3V135.1H156.5L227.5 135.1V101.2H252.2V197.8Z" fill="#A2D3F4"/></svg>`;
|
|
19
|
+
const KIBI_ICON_SRC = `data:image/svg+xml;base64,${Buffer.from(KIBI_LOGO_MARK_SVG, "utf8").toString("base64")}`;
|
|
20
|
+
export const KIBI_ICONS = [
|
|
21
|
+
{
|
|
22
|
+
src: KIBI_ICON_SRC,
|
|
23
|
+
mimeType: "image/svg+xml",
|
|
24
|
+
sizes: ["any"],
|
|
25
|
+
theme: "dark",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
@@ -0,0 +1,107 @@
|
|
|
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/>.
|
|
17
|
+
*/
|
|
18
|
+
import process from "node:process";
|
|
19
|
+
const MAX_FINISHED_JOBS = 32;
|
|
20
|
+
const jobs = new Map();
|
|
21
|
+
let jobCounter = 0;
|
|
22
|
+
function nextJobId(tool) {
|
|
23
|
+
jobCounter += 1;
|
|
24
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
25
|
+
return `job-${tool}-${jobCounter.toString(36)}-${random}`;
|
|
26
|
+
}
|
|
27
|
+
function evictOldestFinished() {
|
|
28
|
+
for (const [id, record] of jobs) {
|
|
29
|
+
if (jobs.size <= MAX_FINISHED_JOBS)
|
|
30
|
+
break;
|
|
31
|
+
if (record.status !== "running") {
|
|
32
|
+
jobs.delete(id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Start a background job for `tool`. The factory's promise runs detached;
|
|
38
|
+
* callers return the `kibi.job.v1` receipt immediately so the MCP request is
|
|
39
|
+
* never held open for the duration of the operation.
|
|
40
|
+
*/
|
|
41
|
+
// implements REQ-002
|
|
42
|
+
export function startJob(tool, factory) {
|
|
43
|
+
const jobId = nextJobId(tool);
|
|
44
|
+
const record = {
|
|
45
|
+
jobId,
|
|
46
|
+
tool,
|
|
47
|
+
startedAt: new Date().toISOString(),
|
|
48
|
+
status: "running",
|
|
49
|
+
};
|
|
50
|
+
jobs.set(jobId, record);
|
|
51
|
+
void (async () => {
|
|
52
|
+
try {
|
|
53
|
+
const result = await factory();
|
|
54
|
+
record.status = "succeeded";
|
|
55
|
+
record.finishedAt = new Date().toISOString();
|
|
56
|
+
record.result =
|
|
57
|
+
result !== null && typeof result === "object"
|
|
58
|
+
? result
|
|
59
|
+
: { value: result };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
record.status = "failed";
|
|
63
|
+
record.finishedAt = new Date().toISOString();
|
|
64
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
evictOldestFinished();
|
|
68
|
+
}
|
|
69
|
+
})().catch((error) => {
|
|
70
|
+
// Defensive: the internal bookkeeping above must never crash the server
|
|
71
|
+
// process via an unhandled rejection.
|
|
72
|
+
process.stderr.write(`[kibi-mcp] job bookkeeping error for ${jobId}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
kibiProtocol: 1,
|
|
76
|
+
jobVersion: "kibi.job.v1",
|
|
77
|
+
jobId,
|
|
78
|
+
tool,
|
|
79
|
+
status: "running",
|
|
80
|
+
pollWith: "kb_job_status",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// implements REQ-002
|
|
84
|
+
export function getJob(jobId) {
|
|
85
|
+
return jobs.get(jobId);
|
|
86
|
+
}
|
|
87
|
+
/** Public JSON shape for kb_job_status responses. */
|
|
88
|
+
// implements REQ-002
|
|
89
|
+
export function jobSnapshot(job) {
|
|
90
|
+
return {
|
|
91
|
+
kibiProtocol: 1,
|
|
92
|
+
jobVersion: "kibi.job.v1",
|
|
93
|
+
jobId: job.jobId,
|
|
94
|
+
tool: job.tool,
|
|
95
|
+
status: job.status,
|
|
96
|
+
startedAt: job.startedAt,
|
|
97
|
+
...(job.finishedAt === undefined ? {} : { finishedAt: job.finishedAt }),
|
|
98
|
+
...(job.result === undefined ? {} : { result: job.result }),
|
|
99
|
+
...(job.error === undefined ? {} : { error: job.error }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Test seam: drop all job state. */
|
|
103
|
+
// implements REQ-002
|
|
104
|
+
export function resetJobsForTests() {
|
|
105
|
+
jobs.clear();
|
|
106
|
+
jobCounter = 0;
|
|
107
|
+
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export function firstDefined(items) {
|
|
3
|
+
return items[0];
|
|
4
|
+
}
|
|
5
|
+
export function describedAnySchema(description) {
|
|
6
|
+
const anySchema = z.any();
|
|
7
|
+
return description ? anySchema.describe(description) : anySchema;
|
|
8
|
+
}
|
|
9
|
+
export function singleLiteralOrAny(literalSchemas, description) {
|
|
10
|
+
const single = firstDefined(literalSchemas);
|
|
11
|
+
if (!single) {
|
|
12
|
+
return describedAnySchema(description);
|
|
13
|
+
}
|
|
14
|
+
return description ? single.describe(description) : single;
|
|
15
|
+
}
|
|
2
16
|
function hasRequiredProperties(value, schema) {
|
|
3
17
|
if (schema === null || typeof schema !== "object")
|
|
4
18
|
return false;
|
|
@@ -64,16 +78,24 @@ export function jsonSchemaToZod(schema) {
|
|
|
64
78
|
}
|
|
65
79
|
const literalSchemas = literals.map((value) => z.literal(value));
|
|
66
80
|
if (literalSchemas.length === 1) {
|
|
67
|
-
|
|
68
|
-
if (!single) {
|
|
69
|
-
return description ? z.any().describe(description) : z.any();
|
|
70
|
-
}
|
|
71
|
-
return description ? single.describe(description) : single;
|
|
81
|
+
return singleLiteralOrAny(literalSchemas, description);
|
|
72
82
|
}
|
|
73
83
|
const union = z.union(literalSchemas);
|
|
74
84
|
return description ? union.describe(description) : union;
|
|
75
85
|
}
|
|
76
|
-
const
|
|
86
|
+
const schemaTypes = Array.isArray(obj.type)
|
|
87
|
+
? obj.type.filter((value) => typeof value === "string")
|
|
88
|
+
: typeof obj.type === "string"
|
|
89
|
+
? [obj.type]
|
|
90
|
+
: [];
|
|
91
|
+
// JSON Schema nullable fields are represented as a type union in the
|
|
92
|
+
// catalog. Build a real Zod union so MCP publishes and validates the same
|
|
93
|
+
// nullability instead of silently degrading the field to z.any().
|
|
94
|
+
if (schemaTypes.length > 1) {
|
|
95
|
+
const variants = schemaTypes.map((type) => jsonSchemaToZod({ ...obj, type }));
|
|
96
|
+
return z.union(variants);
|
|
97
|
+
}
|
|
98
|
+
const schemaType = schemaTypes[0];
|
|
77
99
|
switch (schemaType) {
|
|
78
100
|
case "object": {
|
|
79
101
|
const properties = obj.properties && typeof obj.properties === "object"
|
|
@@ -178,10 +200,12 @@ export function jsonSchemaToZod(schema) {
|
|
|
178
200
|
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
179
201
|
return description ? b.describe(description) : b;
|
|
180
202
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
203
|
+
case "null": {
|
|
204
|
+
const n = z.null();
|
|
183
205
|
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
184
|
-
return description ?
|
|
206
|
+
return description ? n.describe(description) : n;
|
|
185
207
|
}
|
|
208
|
+
default:
|
|
209
|
+
return describedAnySchema(typeof obj.description === "string" ? obj.description : undefined);
|
|
186
210
|
}
|
|
187
211
|
}
|