kibi-mcp 0.14.0 → 0.14.1
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 +86 -1
- package/dist/startup-resolution.js +110 -0
- package/package.json +1 -1
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 {
|
|
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
|
}
|
|
@@ -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
|
+
}
|