kibi-mcp 0.1.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 +59 -0
- package/dist/env.js +99 -0
- package/dist/mcpcat.js +129 -0
- package/dist/server.js +673 -0
- package/dist/tools/branch.js +208 -0
- package/dist/tools/check.js +349 -0
- package/dist/tools/context.js +280 -0
- package/dist/tools/coverage-report.js +91 -0
- package/dist/tools/delete.js +100 -0
- package/dist/tools/derive.js +311 -0
- package/dist/tools/impact.js +70 -0
- package/dist/tools/list-types.js +75 -0
- package/dist/tools/prolog-list.js +176 -0
- package/dist/tools/query-relationships.js +176 -0
- package/dist/tools/query.js +364 -0
- package/dist/tools/suggest-shared-facts.js +138 -0
- package/dist/tools/symbols.js +219 -0
- package/dist/tools/upsert.js +228 -0
- package/dist/tools-config.js +448 -0
- package/dist/workspace.js +126 -0
- package/package.json +43 -0
package/bin/kibi-mcp
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
4
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU Affero General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
*/
|
|
19
|
+
import { startServer } from "../dist/server.js";
|
|
20
|
+
|
|
21
|
+
if (process.env.KIBI_MCP_DEBUG) {
|
|
22
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
23
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
24
|
+
|
|
25
|
+
process.stdout.write = function(chunk, encoding, callback) {
|
|
26
|
+
const str = chunk.toString().trim();
|
|
27
|
+
if (str) {
|
|
28
|
+
originalStderrWrite(`[KIBI-MCP-OUT] ${str}\n`);
|
|
29
|
+
}
|
|
30
|
+
return originalStdoutWrite(chunk, encoding, callback);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
process.stdin.on('data', (data) => {
|
|
34
|
+
const str = data.toString().trim();
|
|
35
|
+
if (str) {
|
|
36
|
+
originalStderrWrite(`[KIBI-MCP-IN] ${str}\n`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
42
|
+
console.error('[KIBI-MCP] Unhandled rejection at promise:', promise);
|
|
43
|
+
console.error('[KIBI-MCP] Reason:', reason);
|
|
44
|
+
if (reason instanceof Error) {
|
|
45
|
+
console.error('[KIBI-MCP] Stack:', reason.stack);
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
process.on('uncaughtException', (error) => {
|
|
51
|
+
console.error('[KIBI-MCP] Uncaught exception:', error.message);
|
|
52
|
+
console.error('[KIBI-MCP] Stack:', error.stack);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
startServer().catch((error) => {
|
|
57
|
+
console.error("Fatal error:", error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
/*
|
|
19
|
+
How to apply this header to source files (examples)
|
|
20
|
+
|
|
21
|
+
1) Prepend header to a single file (POSIX shells):
|
|
22
|
+
|
|
23
|
+
cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
|
|
24
|
+
|
|
25
|
+
2) Apply to multiple files (example: the project's main entry files):
|
|
26
|
+
|
|
27
|
+
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
|
|
28
|
+
if [ -f "$f" ]; then
|
|
29
|
+
cp "$f" "$f".bak
|
|
30
|
+
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
3) Avoid duplicating the header: run a quick guard to only add if missing
|
|
35
|
+
|
|
36
|
+
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
|
|
37
|
+
if [ -f "$f" ]; then
|
|
38
|
+
if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
|
|
39
|
+
cp "$f" "$f".bak
|
|
40
|
+
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
*/
|
|
45
|
+
import fs from "node:fs";
|
|
46
|
+
import { resolveEnvFilePath, resolveWorkspaceRoot } from "./workspace.js";
|
|
47
|
+
const DEFAULT_ENV_FILE = ".env";
|
|
48
|
+
export function loadDefaultEnvFile() {
|
|
49
|
+
const envFileName = process.env.KIBI_ENV_FILE ?? DEFAULT_ENV_FILE;
|
|
50
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
51
|
+
return loadEnvFile({ envFileName, workspaceRoot });
|
|
52
|
+
}
|
|
53
|
+
export function loadEnvFile(options) {
|
|
54
|
+
const { envFileName, workspaceRoot } = options;
|
|
55
|
+
const envFilePath = resolveEnvFilePath(envFileName, workspaceRoot);
|
|
56
|
+
const keysLoaded = [];
|
|
57
|
+
if (!fs.existsSync(envFilePath)) {
|
|
58
|
+
return { loaded: false, envFilePath, keysLoaded };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const raw = fs.readFileSync(envFilePath, "utf8");
|
|
62
|
+
for (const { key, value } of parseEnvContent(raw)) {
|
|
63
|
+
if (!key || Object.prototype.hasOwnProperty.call(process.env, key)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
process.env[key] = value;
|
|
67
|
+
keysLoaded.push(key);
|
|
68
|
+
}
|
|
69
|
+
return { loaded: true, envFilePath, keysLoaded };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error(`[Kibi] Unable to load environment file ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
return { loaded: false, envFilePath, keysLoaded };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function parseEnvContent(content) {
|
|
77
|
+
const lines = content.split(/\r?\n/);
|
|
78
|
+
const entries = [];
|
|
79
|
+
for (const rawLine of lines) {
|
|
80
|
+
const line = rawLine.trim();
|
|
81
|
+
if (!line || line.startsWith("#")) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const eqIndex = line.indexOf("=");
|
|
85
|
+
if (eqIndex <= 0) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const key = line.substring(0, eqIndex).trim();
|
|
89
|
+
let value = line.substring(eqIndex + 1).trim();
|
|
90
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
91
|
+
value = value.slice(1, -1);
|
|
92
|
+
}
|
|
93
|
+
else if (value.startsWith("'") && value.endsWith("'")) {
|
|
94
|
+
value = value.slice(1, -1);
|
|
95
|
+
}
|
|
96
|
+
entries.push({ key, value });
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
99
|
+
}
|
package/dist/mcpcat.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
/*
|
|
19
|
+
How to apply this header to source files (examples)
|
|
20
|
+
|
|
21
|
+
1) Prepend header to a single file (POSIX shells):
|
|
22
|
+
|
|
23
|
+
cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
|
|
24
|
+
|
|
25
|
+
2) Apply to multiple files (example: the project's main entry files):
|
|
26
|
+
|
|
27
|
+
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
|
|
28
|
+
if [ -f "$f" ]; then
|
|
29
|
+
cp "$f" "$f".bak
|
|
30
|
+
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
3) Avoid duplicating the header: run a quick guard to only add if missing
|
|
35
|
+
|
|
36
|
+
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
|
|
37
|
+
if [ -f "$f" ]; then
|
|
38
|
+
if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
|
|
39
|
+
cp "$f" "$f".bak
|
|
40
|
+
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
*/
|
|
45
|
+
import { createHash } from "node:crypto";
|
|
46
|
+
import fs from "node:fs";
|
|
47
|
+
import os from "node:os";
|
|
48
|
+
import path from "node:path";
|
|
49
|
+
import * as mcpcat from "mcpcat";
|
|
50
|
+
import { resolveWorkspaceRoot } from "./workspace.js";
|
|
51
|
+
const projectId = (process.env.MCPCAT_PROJECT_ID ?? "").trim();
|
|
52
|
+
const trackedIdentity = resolveTrackedIdentity();
|
|
53
|
+
/**
|
|
54
|
+
* Attach mcpcat analytics tracking to the MCP server.
|
|
55
|
+
*
|
|
56
|
+
* NOTE ON SESSIONS: With stdio transport, many MCP clients (including OpenCode)
|
|
57
|
+
* spawn a new process for each tool call. This means each tool call gets a new
|
|
58
|
+
* MCP session ID, resulting in single-tool-call "sessions" in mcpcat.
|
|
59
|
+
*
|
|
60
|
+
* This is expected behavior for stdio transport - each process IS a different
|
|
61
|
+
* session. User identity (via the identify() function) still provides useful
|
|
62
|
+
* aggregation across all tool calls from the same user/machine.
|
|
63
|
+
*
|
|
64
|
+
* For true session aggregation, clients would need to either:
|
|
65
|
+
* 1. Use HTTP transport with persistent connections
|
|
66
|
+
* 2. Maintain long-lived stdio connections across multiple tool calls
|
|
67
|
+
* 3. Implement custom session headers
|
|
68
|
+
*/
|
|
69
|
+
export function attachMcpcat(server) {
|
|
70
|
+
if (!projectId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
mcpcat.track(server, projectId, {
|
|
75
|
+
identify: async () => trackedIdentity,
|
|
76
|
+
enableReportMissing: false, // Don't add get_more_tools tool - it's internal
|
|
77
|
+
enableTracing: true,
|
|
78
|
+
enableToolCallContext: false, // Don't inject context parameter into tools
|
|
79
|
+
});
|
|
80
|
+
if (process.env.KIBI_MCP_DEBUG) {
|
|
81
|
+
console.error(`[KIBI-MCP] MCPcat tracking enabled for project ${projectId}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error(`[KIBI-MCP] MCPcat tracking attach failed: ${details}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function resolveTrackedIdentity() {
|
|
90
|
+
const explicitUserId = readEnv("MCPCAT_USER_ID");
|
|
91
|
+
if (explicitUserId) {
|
|
92
|
+
return {
|
|
93
|
+
userId: explicitUserId,
|
|
94
|
+
userName: readEnv("MCPCAT_USER_NAME") ?? "local-operator",
|
|
95
|
+
userData: { identitySource: "env" },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const repoRoot = findRepoRoot(resolveWorkspaceRoot());
|
|
99
|
+
const repoName = path.basename(repoRoot);
|
|
100
|
+
const username = readEnv("USER") ?? readEnv("USERNAME") ?? "unknown-user";
|
|
101
|
+
const host = os.hostname() || "unknown-host";
|
|
102
|
+
const stableId = createHash("sha256")
|
|
103
|
+
.update(`${host}:${username}:${repoRoot}`)
|
|
104
|
+
.digest("hex")
|
|
105
|
+
.slice(0, 24);
|
|
106
|
+
return {
|
|
107
|
+
userId: `anon_${stableId}`,
|
|
108
|
+
userName: `local-${repoName}`,
|
|
109
|
+
userData: { identitySource: "host-user-repo-hash", repo: repoName },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function readEnv(name) {
|
|
113
|
+
const value = process.env[name]?.trim();
|
|
114
|
+
return value ? value : null;
|
|
115
|
+
}
|
|
116
|
+
function findRepoRoot(startDir) {
|
|
117
|
+
let current = path.resolve(startDir);
|
|
118
|
+
while (true) {
|
|
119
|
+
const gitMarker = path.join(current, ".git");
|
|
120
|
+
if (fs.existsSync(gitMarker)) {
|
|
121
|
+
return current;
|
|
122
|
+
}
|
|
123
|
+
const parent = path.dirname(current);
|
|
124
|
+
if (parent === current) {
|
|
125
|
+
return path.resolve(startDir);
|
|
126
|
+
}
|
|
127
|
+
current = parent;
|
|
128
|
+
}
|
|
129
|
+
}
|