kibi-mcp 0.3.2 → 0.3.3

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.
@@ -0,0 +1,95 @@
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 fs from "node:fs";
19
+ import path from "node:path";
20
+ import { resolveWorkspaceRoot } from "./workspace.js";
21
+ const DIAGNOSTIC_MODE_FLAG = "--diagnostic-mode";
22
+ /**
23
+ * Whether diagnostic mode is enabled via CLI flag.
24
+ * Set during server startup and never changes at runtime.
25
+ */
26
+ export const DIAGNOSTIC_MODE_ENABLED = process.argv.includes(DIAGNOSTIC_MODE_FLAG);
27
+ /**
28
+ * Path to the diagnostic usage log file.
29
+ * Only valid when DIAGNOSTIC_MODE_ENABLED is true.
30
+ */
31
+ let diagnosticUsageLogPath = null;
32
+ /**
33
+ * Initialize diagnostic mode: set up usage.log path.
34
+ * Called once during server startup.
35
+ */
36
+ export function initializeDiagnosticMode() {
37
+ if (DIAGNOSTIC_MODE_ENABLED) {
38
+ const workspaceRoot = resolveWorkspaceRoot();
39
+ diagnosticUsageLogPath = path.join(workspaceRoot, ".kb", "usage.log");
40
+ process.env.KIBI_MCP_DIAGNOSTIC_MODE = "1";
41
+ }
42
+ }
43
+ /**
44
+ * Append a JSON line to the usage.log file.
45
+ * No-op if diagnostic mode is not enabled.
46
+ */
47
+ export function appendUsageLogLine(entry) {
48
+ if (!DIAGNOSTIC_MODE_ENABLED || !diagnosticUsageLogPath) {
49
+ return;
50
+ }
51
+ const logDir = path.dirname(diagnosticUsageLogPath);
52
+ fs.mkdirSync(logDir, { recursive: true });
53
+ fs.appendFileSync(diagnosticUsageLogPath, `${JSON.stringify(entry)}\n`, {
54
+ encoding: "utf8",
55
+ });
56
+ }
57
+ /**
58
+ * Schema for _diagnostic_telemetry field added to tool inputs in diagnostic mode.
59
+ */
60
+ export const DIAGNOSTIC_TELEMETRY_SCHEMA = {
61
+ type: "object",
62
+ description: "REQUIRED when diagnostic mode is on. Provide self-reflection metadata about this tool call.",
63
+ properties: {
64
+ is_autonomous: {
65
+ type: "boolean",
66
+ description: "Set to TRUE if you are calling this tool on your own initiative to retrieve context. Set to FALSE if the user explicitly commanded you to use the knowledge base.",
67
+ },
68
+ reasoning: {
69
+ type: "string",
70
+ description: "A brief, 1-2 sentence internal thought explaining exactly why you are calling this tool right now and what information you expect to get.",
71
+ },
72
+ confidence_score: {
73
+ type: "number",
74
+ description: "A score from 0.0 to 1.0 representing your confidence that the exact parameters, IDs, or tags you provided will yield a successful result.",
75
+ },
76
+ attempt_number: {
77
+ type: "integer",
78
+ description: "If you are retrying this exact task because a previous tool call failed or returned empty results, increment this number (start at 1).",
79
+ },
80
+ missing_context: {
81
+ type: "string",
82
+ description: "If you had to split your task into multiple steps because this tool lacks a specific filtering or querying capability, describe what parameter is missing. Otherwise, leave empty.",
83
+ },
84
+ },
85
+ };
86
+ /**
87
+ * Extract business args and telemetry from tool call arguments.
88
+ */
89
+ export function extractToolCallPayload(args) {
90
+ const { _diagnostic_telemetry, ...businessArgs } = args;
91
+ const telemetry = _diagnostic_telemetry && typeof _diagnostic_telemetry === "object"
92
+ ? _diagnostic_telemetry
93
+ : null;
94
+ return { businessArgs, telemetry };
95
+ }
@@ -17,12 +17,13 @@
17
17
  */
18
18
  import process from "node:process";
19
19
  import { z } from "zod";
20
+ import { DIAGNOSTIC_MODE_ENABLED, appendUsageLogLine, extractToolCallPayload, } from "../diagnostics.js";
20
21
  import { TOOLS } from "../tools-config.js";
21
22
  import { handleKbCheck } from "../tools/check.js";
22
23
  import { handleKbDelete } from "../tools/delete.js";
23
24
  import { handleKbQuery } from "../tools/query.js";
24
25
  import { handleKbUpsert } from "../tools/upsert.js";
25
- import { ensureProlog, inFlightRequests, isShuttingDown } from "./session.js";
26
+ import { activeBranchName, ensureProlog, inFlightRequests, isShuttingDown, prologProcess, } from "./session.js";
26
27
  const ACTIVE_TOOLS = TOOLS;
27
28
  function debugLog(...args) {
28
29
  if (process.env.KIBI_MCP_DEBUG) {
@@ -131,12 +132,16 @@ export function jsonSchemaToZod(schema) {
131
132
  }
132
133
  function addTool(server, name, description, inputSchema, handler) {
133
134
  const wrappedHandler = async (args) => {
135
+ const startedAt = new Date();
136
+ // Extract telemetry in diagnostic mode
137
+ const { businessArgs, telemetry } = DIAGNOSTIC_MODE_ENABLED
138
+ ? extractToolCallPayload(args)
139
+ : { businessArgs: args, telemetry: null };
134
140
  try {
135
141
  // Validate that args is a valid object
136
142
  if (typeof args !== "object" || args === null) {
137
143
  throw new Error(`Invalid arguments for tool ${name}: expected object, got ${typeof args}`);
138
144
  }
139
- const businessArgs = args;
140
145
  // Check if shutting down before processing
141
146
  if (isShuttingDown) {
142
147
  throw new Error(`Tool ${name} rejected: server is shutting down`);
@@ -155,7 +160,47 @@ function addTool(server, name, description, inputSchema, handler) {
155
160
  inFlightRequests.set(requestId, handlerPromise);
156
161
  try {
157
162
  // Execute handler
158
- return await handlerPromise;
163
+ const result = await handlerPromise;
164
+ // Log usage in diagnostic mode
165
+ if (DIAGNOSTIC_MODE_ENABLED) {
166
+ const finishedAt = new Date();
167
+ appendUsageLogLine({
168
+ timestamp: finishedAt.toISOString(),
169
+ request_id: requestId,
170
+ tool: name,
171
+ telemetry,
172
+ business_args: businessArgs,
173
+ status: "success",
174
+ started_at: startedAt.toISOString(),
175
+ finished_at: finishedAt.toISOString(),
176
+ duration_ms: finishedAt.getTime() - startedAt.getTime(),
177
+ prolog_pid: prologProcess?.getPid() ?? null,
178
+ active_branch: activeBranchName,
179
+ });
180
+ }
181
+ return result;
182
+ }
183
+ catch (error) {
184
+ // Log error in diagnostic mode
185
+ if (DIAGNOSTIC_MODE_ENABLED) {
186
+ const finishedAt = new Date();
187
+ const err = error instanceof Error ? error : new Error(String(error));
188
+ appendUsageLogLine({
189
+ timestamp: finishedAt.toISOString(),
190
+ request_id: requestId,
191
+ tool: name,
192
+ telemetry,
193
+ business_args: businessArgs,
194
+ status: "error",
195
+ started_at: startedAt.toISOString(),
196
+ finished_at: finishedAt.toISOString(),
197
+ duration_ms: finishedAt.getTime() - startedAt.getTime(),
198
+ prolog_pid: prologProcess?.getPid() ?? null,
199
+ active_branch: activeBranchName,
200
+ error_message: err.message,
201
+ });
202
+ }
203
+ throw error;
159
204
  }
160
205
  finally {
161
206
  // Always clean up from Map when done (success or failure)
package/dist/server.js CHANGED
@@ -15,17 +15,24 @@
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 { readFileSync } from "node:fs";
18
19
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { initializeDiagnosticMode } from "./diagnostics.js";
20
22
  import { loadDefaultEnvFile } from "./env.js";
21
23
  import { setupDocsAndPrompts } from "./server/docs.js";
22
24
  import { registerAllTools } from "./server/tools.js";
23
25
  import { connectTransport, setupTransportHandlers, } from "./server/transport.js";
26
+ // Read version from package.json to prevent drift
27
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
28
+ const VERSION = packageJson.version ?? "0.1.0";
24
29
  export async function startServer() {
25
30
  // Load environment configuration
26
31
  loadDefaultEnvFile();
32
+ // Initialize diagnostic mode if --diagnostic-mode flag is present
33
+ initializeDiagnosticMode();
27
34
  // Create MCP server
28
- const server = new McpServer({ name: "kibi-mcp", version: "0.2.1" });
35
+ const server = new McpServer({ name: "kibi-mcp", version: VERSION });
29
36
  // Setup documentation resources and prompts
30
37
  setupDocsAndPrompts(server);
31
38
  // Register all KB tools
@@ -45,9 +45,7 @@ export async function handleKbQuery(prolog, args) {
45
45
  goal = `findall(['${safeId}',Type,Props], kb_entity('${safeId}', Type, Props), Results)`;
46
46
  }
47
47
  else if (tags && tags.length > 0) {
48
- // TODO: Reintroduce server-side (Prolog) tag filtering once normalization
49
- // issues with tag list formats are resolved, to avoid fetching all entities
50
- // before filtering in JS for large knowledge bases.
48
+ // JS-side fallback until REQ-mcp-tag-filtering-server-side is implemented.
51
49
  if (type) {
52
50
  const safeType = escapeAtomContent(type);
53
51
  goal = `findall([Id,'${safeType}',Props], kb_entity(Id, '${safeType}', Props), Results)`;
@@ -14,8 +14,9 @@
14
14
 
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
- */
18
- export const TOOLS = [
17
+ */
18
+ import { DIAGNOSTIC_MODE_ENABLED, DIAGNOSTIC_TELEMETRY_SCHEMA, } from "./diagnostics.js";
19
+ const BASE_TOOLS = [
19
20
  // implements REQ-002
20
21
  {
21
22
  name: "kb_query",
@@ -216,3 +217,31 @@ export const TOOLS = [
216
217
  },
217
218
  },
218
219
  ];
220
+ /**
221
+ * Inject _diagnostic_telemetry schema into tool inputs when diagnostic mode is enabled.
222
+ */
223
+ function withDiagnosticTelemetrySchema(tools) {
224
+ return tools.map((tool) => {
225
+ const schema = tool.inputSchema;
226
+ const properties = schema.properties && typeof schema.properties === "object"
227
+ ? schema.properties
228
+ : {};
229
+ return {
230
+ ...tool,
231
+ inputSchema: {
232
+ ...schema,
233
+ properties: {
234
+ ...properties,
235
+ _diagnostic_telemetry: DIAGNOSTIC_TELEMETRY_SCHEMA,
236
+ },
237
+ },
238
+ };
239
+ });
240
+ }
241
+ /**
242
+ * Active tools list.
243
+ * In diagnostic mode, all tools include the _diagnostic_telemetry parameter.
244
+ */
245
+ export const TOOLS = DIAGNOSTIC_MODE_ENABLED
246
+ ? withDiagnosticTelemetrySchema(BASE_TOOLS)
247
+ : BASE_TOOLS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "dependencies": {
5
5
  "@modelcontextprotocol/sdk": "^1.26.0",
6
6
  "ajv": "^8.18.0",
@@ -40,7 +40,8 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "latest",
43
- "@types/node": "latest"
43
+ "@types/node": "latest",
44
+ "typescript": "^5.7.0"
44
45
  },
45
46
  "license": "AGPL-3.0-or-later",
46
47
  "author": "Piotr Franczyk",