kibi-mcp 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/kibi-mcp CHANGED
@@ -18,6 +18,59 @@
18
18
  */
19
19
  const args = globalThis.Bun?.argv?.slice(2) ?? process.argv.slice(2);
20
20
 
21
+ async function importStartupResolution() {
22
+ try {
23
+ return await import("../dist/startup-resolution.js");
24
+ } catch {
25
+ return await import("../src/startup-resolution.ts");
26
+ }
27
+ }
28
+
29
+ async function serverEntrypointUrl() {
30
+ const { existsSync } = await import("node:fs");
31
+ const distUrl = new URL("../dist/server.js", import.meta.url);
32
+ if (existsSync(distUrl)) {
33
+ return distUrl.href;
34
+ }
35
+ return new URL("../src/server.ts", import.meta.url).href;
36
+ }
37
+
38
+ async function buildResolution() {
39
+ const {
40
+ compareMcpResolution,
41
+ formatResolutionJson,
42
+ readRunningPackageInfo,
43
+ resolveProjectLocalMcp,
44
+ } = await importStartupResolution();
45
+ const running = readRunningPackageInfo(await serverEntrypointUrl());
46
+ const projectLocal = resolveProjectLocalMcp(process.cwd());
47
+ const comparison = compareMcpResolution(running, projectLocal);
48
+ const result = {
49
+ packageName: "kibi-mcp",
50
+ packageVersion: running.version,
51
+ version: running.version,
52
+ resolved: running.entrypoint,
53
+ cwd: process.cwd(),
54
+ running,
55
+ projectLocal,
56
+ ...comparison,
57
+ };
58
+
59
+ return { result, json: formatResolutionJson(result) };
60
+ }
61
+
62
+ async function importServer(entrypoint) {
63
+ if (entrypoint) {
64
+ const { pathToFileURL } = await import("node:url");
65
+ return await import(pathToFileURL(entrypoint).href);
66
+ }
67
+ try {
68
+ return await import("../dist/server.js");
69
+ } catch {
70
+ return await import("../src/server.ts");
71
+ }
72
+ }
73
+
21
74
  function printHelp() {
22
75
  process.stdout.write(`Usage: kibi-mcp [options]
23
76
 
@@ -25,6 +78,7 @@ Starts the Kibi MCP server over stdio.
25
78
 
26
79
  Options:
27
80
  --diagnostic-mode enable diagnostic logging to .kb/usage.log
81
+ --print-resolution print kibi-mcp startup resolution JSON and exit
28
82
  -h, --help show help
29
83
  `);
30
84
  }
@@ -33,6 +87,11 @@ if (args.includes("-h") || args.includes("--help")) {
33
87
  printHelp();
34
88
  process.exitCode = 0;
35
89
  } else {
90
+ if (args.includes("--print-resolution")) {
91
+ const { json } = await buildResolution();
92
+ process.stdout.write(json);
93
+ process.exitCode = 0;
94
+ } else {
36
95
  if (args.includes("--diagnostic-mode") && !process.argv.includes("--diagnostic-mode")) {
37
96
  process.argv.push("--diagnostic-mode");
38
97
  }
@@ -43,7 +102,31 @@ if (args.includes("-h") || args.includes("--help")) {
43
102
  process.env.KIBI_MCP_DIAGNOSTIC_MODE = "1";
44
103
  }
45
104
 
46
- const { startServer } = await import("../dist/server.js");
105
+ const { result, json } = await buildResolution();
106
+ if (process.env.KIBI_MCP_DEBUG === "1" || args.includes("--diagnostic-mode")) {
107
+ process.stderr.write(json);
108
+ }
109
+
110
+ if (result.stale) {
111
+ if (result.projectLocal && process.env.KIBI_MCP_PROJECT_LOCAL_REENTRY !== "1") {
112
+ process.env.KIBI_MCP_PROJECT_LOCAL_REENTRY = "1";
113
+ process.stderr.write(
114
+ `[KIBI-MCP] Re-entering project-local kibi-mcp at ${result.projectLocal.entrypoint}; stale running path was ${result.running.entrypoint}\n`,
115
+ );
116
+ const { startServer } = await importServer(result.projectLocal.entrypoint);
117
+ startServer().catch((error) => {
118
+ console.error("Fatal error:", error);
119
+ process.exit(1);
120
+ });
121
+ } else {
122
+ const localEntrypoint = result.projectLocal?.entrypoint ?? "<not resolved>";
123
+ console.error(
124
+ `[KIBI-MCP] Refusing stale kibi-mcp startup. project-local entrypoint: ${localEntrypoint}; running entrypoint: ${result.running.entrypoint}`,
125
+ );
126
+ process.exit(1);
127
+ }
128
+ } else {
129
+ const { startServer } = await importServer();
47
130
 
48
131
  if (process.env.KIBI_MCP_DEBUG) {
49
132
  const originalStdoutWrite = process.stdout.write.bind(process.stdout);
@@ -84,4 +167,6 @@ if (args.includes("-h") || args.includes("--help")) {
84
167
  console.error("Fatal error:", error);
85
168
  process.exit(1);
86
169
  });
170
+ }
171
+ }
87
172
  }
@@ -33,6 +33,7 @@ function renderToolsDoc() {
33
33
  const required = Array.isArray(tool.inputSchema?.required)
34
34
  ? tool.inputSchema.required.join(", ")
35
35
  : "none";
36
+ lines.push(`| \`${tool.name}\` | ${tool.description} | ${required || "none"} |`);
36
37
  }
37
38
  lines.push("");
38
39
  lines.push("Modeling note: Kibi has eight core entity types grouped into common authoring (req, scenario, test, fact) and supporting/system (adr, flag, event, symbol).");
@@ -10,6 +10,7 @@ import { handleKbGraph } from "../tools/graph.js";
10
10
  import { handleKbQuery } from "../tools/query.js";
11
11
  import { handleKbSearch } from "../tools/search.js";
12
12
  import { handleKbStatus } from "../tools/status.js";
13
+ import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
13
14
  import { handleKbUpsert } from "../tools/upsert.js";
14
15
  import { handleKbModelRequirement, } from "../tools/model-requirement.js";
15
16
  import { handleKbAutopilotGenerate, } from "../tools/autopilot-generate.js";
@@ -59,6 +60,9 @@ const DEFAULT_TOOLS_RUNTIME = {
59
60
  handleKbQuery,
60
61
  handleKbSearch,
61
62
  handleKbStatus,
63
+ handleKbSkillsList,
64
+ handleKbSkillsLoad,
65
+ handleKbSkillsRead,
62
66
  handleKbUpsert,
63
67
  handleKbModelRequirement,
64
68
  handleKbAutopilotGenerate,
@@ -299,6 +303,9 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
299
303
  const prolog = await runtime.ensureProlog();
300
304
  return runtime.handleKbStatus(prolog, args);
301
305
  }, runtime);
306
+ addTool(server, "kb_skills_list", toolDef("kb_skills_list").description, toolDef("kb_skills_list").inputSchema, async (args) => runtime.handleKbSkillsList(args), runtime);
307
+ addTool(server, "kb_skills_load", toolDef("kb_skills_load").description, toolDef("kb_skills_load").inputSchema, async (args) => runtime.handleKbSkillsLoad(args), runtime);
308
+ addTool(server, "kb_skills_read", toolDef("kb_skills_read").description, toolDef("kb_skills_read").inputSchema, async (args) => runtime.handleKbSkillsRead(args), runtime);
302
309
  addTool(server, "kb_find_gaps", toolDef("kb_find_gaps").description, toolDef("kb_find_gaps").inputSchema, async (args) => {
303
310
  const prolog = await runtime.ensureProlog();
304
311
  return runtime.handleKbFindGaps(prolog, args);
@@ -0,0 +1,110 @@
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 { existsSync, readFileSync, realpathSync } from "node:fs";
19
+ import { createRequire } from "node:module";
20
+ import path from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ const PACKAGE_NAME = "kibi-mcp";
23
+ const FORBIDDEN_VERSION = "0.13.0";
24
+ function toEntrypointPath(entrypointUrl) {
25
+ if (entrypointUrl.startsWith("file:")) {
26
+ return fileURLToPath(entrypointUrl);
27
+ }
28
+ return path.resolve(entrypointUrl);
29
+ }
30
+ function readJson(filePath) {
31
+ return JSON.parse(readFileSync(filePath, "utf8"));
32
+ }
33
+ function nearestPackageJson(startPath) {
34
+ let current = path.dirname(startPath);
35
+ while (true) {
36
+ const candidate = path.join(current, "package.json");
37
+ if (existsSync(candidate)) {
38
+ return candidate;
39
+ }
40
+ const parent = path.dirname(current);
41
+ if (parent === current) {
42
+ throw new Error(`Unable to find package.json for ${startPath}`);
43
+ }
44
+ current = parent;
45
+ }
46
+ }
47
+ function packageInfoFromEntrypoint(entrypoint) {
48
+ const normalizedEntrypoint = path.resolve(entrypoint);
49
+ const packageJsonPath = nearestPackageJson(normalizedEntrypoint);
50
+ const packageJson = readJson(packageJsonPath);
51
+ if (packageJson.name && packageJson.name !== PACKAGE_NAME) {
52
+ throw new Error(`Resolved package ${packageJson.name} for ${normalizedEntrypoint}; expected ${PACKAGE_NAME}`);
53
+ }
54
+ return {
55
+ packageRoot: path.dirname(packageJsonPath),
56
+ version: packageJson.version ?? "unknown",
57
+ entrypoint: normalizedEntrypoint,
58
+ };
59
+ }
60
+ export function readRunningPackageInfo(entrypointUrl) {
61
+ return packageInfoFromEntrypoint(toEntrypointPath(entrypointUrl));
62
+ }
63
+ export function resolveProjectLocalMcp(cwd) {
64
+ try {
65
+ const projectRequire = createRequire(path.join(path.resolve(cwd), "package.json"));
66
+ const resolved = projectRequire.resolve(PACKAGE_NAME);
67
+ const entrypoint = realpathSync.native?.(resolved) ?? realpathSync(resolved);
68
+ return packageInfoFromEntrypoint(entrypoint);
69
+ }
70
+ catch (error) {
71
+ const code = error.code;
72
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
73
+ return null;
74
+ }
75
+ return null;
76
+ }
77
+ }
78
+ export function compareMcpResolution(running, projectLocal) {
79
+ const diagnosticText = JSON.stringify({ running, projectLocal });
80
+ const forbiddenVersionObserved = diagnosticText.includes(FORBIDDEN_VERSION);
81
+ if (!projectLocal) {
82
+ return {
83
+ stale: false,
84
+ reason: "no project-local kibi-mcp resolved",
85
+ forbiddenVersionObserved,
86
+ };
87
+ }
88
+ if (running.version !== projectLocal.version) {
89
+ return {
90
+ stale: true,
91
+ reason: `version mismatch: running ${running.version}, project-local ${projectLocal.version}`,
92
+ forbiddenVersionObserved,
93
+ };
94
+ }
95
+ if (path.resolve(running.packageRoot) !== path.resolve(projectLocal.packageRoot)) {
96
+ return {
97
+ stale: true,
98
+ reason: `package root mismatch: running ${running.packageRoot}, project-local ${projectLocal.packageRoot}`,
99
+ forbiddenVersionObserved,
100
+ };
101
+ }
102
+ return {
103
+ stale: false,
104
+ reason: "running kibi-mcp matches project-local kibi-mcp",
105
+ forbiddenVersionObserved,
106
+ };
107
+ }
108
+ export function formatResolutionJson(result) {
109
+ return `${JSON.stringify(result, null, 2)}\n`;
110
+ }
@@ -0,0 +1,71 @@
1
+ import { createHash } from "node:crypto";
2
+ import { listBundledSkills, loadBundledSkill, readBundledSkillResource, } from "kibi-cli/skills";
3
+ // implements REQ-001
4
+ export async function handleKbSkillsList(_args) {
5
+ try {
6
+ const skills = listBundledSkills();
7
+ const ids = skills.map((skill) => skill.id).join(", ") || "none";
8
+ return {
9
+ content: [{ type: "text", text: `Found ${skills.length} bundled skills: ${ids}` }],
10
+ structuredContent: { skills },
11
+ };
12
+ }
13
+ catch (error) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ throw new Error(`Skills list failed: ${message}`);
16
+ }
17
+ }
18
+ // implements REQ-001
19
+ export async function handleKbSkillsLoad(args) {
20
+ try {
21
+ assertNonEmptyString(args.id, "id");
22
+ const bundle = loadBundledSkill(args.id);
23
+ const resources = bundle.manifest.resources ?? [];
24
+ const payload = {
25
+ metadata: bundle.manifest,
26
+ body: bundle.body,
27
+ resources,
28
+ contentHash: createHash("sha256").update(bundle.body, "utf8").digest("hex"),
29
+ sourceType: "bundled",
30
+ };
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `Loaded bundled skill ${bundle.manifest.id} with ${resources.length} resources`,
36
+ },
37
+ ],
38
+ structuredContent: payload,
39
+ };
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ throw new Error(`Skills load failed: ${message}`);
44
+ }
45
+ }
46
+ // implements REQ-001
47
+ export async function handleKbSkillsRead(args) {
48
+ try {
49
+ assertNonEmptyString(args.id, "id");
50
+ assertNonEmptyString(args.resource, "resource");
51
+ const resourceContent = readBundledSkillResource(args.id, args.resource);
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: `Read bundled skill resource ${args.id}/${args.resource}`,
57
+ },
58
+ ],
59
+ structuredContent: { content: resourceContent },
60
+ };
61
+ }
62
+ catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ throw new Error(`Skills read failed: ${message}`);
65
+ }
66
+ }
67
+ function assertNonEmptyString(value, field) {
68
+ if (typeof value !== "string" || value.trim() === "") {
69
+ throw new Error(`${field} must be a non-empty string`);
70
+ }
71
+ }
@@ -110,6 +110,46 @@ const BASE_TOOLS = [
110
110
  properties: {},
111
111
  },
112
112
  },
113
+ {
114
+ name: "kb_skills_list",
115
+ description: "List bundled Kibi agent skills available for progressive disclosure. Read-only; does not mutate the KB or require Prolog.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {},
119
+ },
120
+ },
121
+ {
122
+ name: "kb_skills_load",
123
+ description: "Load a bundled Kibi agent skill by ID, returning its manifest metadata, Markdown body, declared resources, content hash, and source type. Read-only; does not execute scripts or require Prolog.",
124
+ inputSchema: {
125
+ type: "object",
126
+ required: ["id"],
127
+ properties: {
128
+ id: {
129
+ type: "string",
130
+ description: "Bundled skill ID to load. Example: 'kibi-usage'.",
131
+ },
132
+ },
133
+ },
134
+ },
135
+ {
136
+ name: "kb_skills_read",
137
+ description: "Read a declared resource from a bundled Kibi agent skill. Resource paths are restricted to the skill manifest; arbitrary file paths are not exposed. Read-only; does not require Prolog.",
138
+ inputSchema: {
139
+ type: "object",
140
+ required: ["id", "resource"],
141
+ properties: {
142
+ id: {
143
+ type: "string",
144
+ description: "Bundled skill ID. Example: 'kibi-usage'.",
145
+ },
146
+ resource: {
147
+ type: "string",
148
+ description: "Manifest-declared resource path to read. Example: 'resources/workflows.md'.",
149
+ },
150
+ },
151
+ },
152
+ },
113
153
  {
114
154
  name: "kb_find_gaps",
115
155
  description: "Run bulk missing/present relationship analysis over KB entities. Use for questions like which requirements lack scenarios or tests. No mutation side effects.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-mcp",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "dependencies": {
5
5
  "@modelcontextprotocol/sdk": "^1.26.0",
6
6
  "ajv": "^8.18.0",
@@ -9,7 +9,7 @@
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.11.0",
12
+ "kibi-cli": "^0.11.1",
13
13
  "kibi-core": "^0.5.3",
14
14
  "mcpcat": "^0.1.12",
15
15
  "ts-morph": "^23.0.0",