@neurynae/toolcairn-mcp 0.10.12 → 0.10.14
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/dist/index.js +388 -64
- package/package.json +2 -3
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -336,8 +336,8 @@ var require_logger = __commonJS({
|
|
|
336
336
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
337
337
|
};
|
|
338
338
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
339
|
-
exports.createMcpLogger =
|
|
340
|
-
exports.createLogger =
|
|
339
|
+
exports.createMcpLogger = createMcpLogger28;
|
|
340
|
+
exports.createLogger = createMcpLogger28;
|
|
341
341
|
var node_os_1 = __require("os");
|
|
342
342
|
var node_path_1 = __require("path");
|
|
343
343
|
var pino_1 = __importDefault(__require("pino"));
|
|
@@ -361,7 +361,7 @@ var require_logger = __commonJS({
|
|
|
361
361
|
"*.apiKey",
|
|
362
362
|
"*.api_key"
|
|
363
363
|
];
|
|
364
|
-
function
|
|
364
|
+
function createMcpLogger28(opts) {
|
|
365
365
|
const level = opts.level ?? process.env.LOG_LEVEL ?? (process.env.NODE_ENV !== "production" ? "debug" : "info");
|
|
366
366
|
const pinoOpts = {
|
|
367
367
|
name: opts.name,
|
|
@@ -407,14 +407,14 @@ var require_mcp_error_wrapper = __commonJS({
|
|
|
407
407
|
exports.withErrorHandling = withErrorHandling2;
|
|
408
408
|
var error_codes_js_1 = require_error_codes();
|
|
409
409
|
var errors_js_1 = require_errors();
|
|
410
|
-
function withErrorHandling2(toolName,
|
|
410
|
+
function withErrorHandling2(toolName, logger28, handler) {
|
|
411
411
|
return async (args) => {
|
|
412
412
|
try {
|
|
413
413
|
return await handler(args);
|
|
414
414
|
} catch (err) {
|
|
415
415
|
if (err instanceof errors_js_1.AppError) {
|
|
416
416
|
const logLevel = err.severity === "critical" || err.severity === "high" ? "error" : "warn";
|
|
417
|
-
|
|
417
|
+
logger28[logLevel]({ err, tool: toolName }, `Tool ${toolName} failed: ${err.message}`);
|
|
418
418
|
return {
|
|
419
419
|
content: [
|
|
420
420
|
{
|
|
@@ -429,7 +429,7 @@ var require_mcp_error_wrapper = __commonJS({
|
|
|
429
429
|
isError: true
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
|
-
|
|
432
|
+
logger28.error({ err, tool: toolName }, `Unexpected error in tool ${toolName}`);
|
|
433
433
|
return {
|
|
434
434
|
content: [
|
|
435
435
|
{
|
|
@@ -512,7 +512,7 @@ var require_dist2 = __commonJS({
|
|
|
512
512
|
// src/index.prod.ts
|
|
513
513
|
init_esm_shims();
|
|
514
514
|
var import_config5 = __toESM(require_dist(), 1);
|
|
515
|
-
var
|
|
515
|
+
var import_errors29 = __toESM(require_dist2(), 1);
|
|
516
516
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
517
517
|
|
|
518
518
|
// ../../packages/remote/dist/index.js
|
|
@@ -939,7 +939,7 @@ async function pollForToken(apiUrl, deviceCode, intervalSec) {
|
|
|
939
939
|
}
|
|
940
940
|
}
|
|
941
941
|
function sleep(ms) {
|
|
942
|
-
return new Promise((
|
|
942
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
943
943
|
}
|
|
944
944
|
|
|
945
945
|
// src/index.prod.ts
|
|
@@ -1316,10 +1316,33 @@ async function writeConfig(projectRoot, config5) {
|
|
|
1316
1316
|
init_esm_shims();
|
|
1317
1317
|
var import_errors6 = __toESM(require_dist2(), 1);
|
|
1318
1318
|
import { appendFile, mkdir as mkdir3, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/promises";
|
|
1319
|
+
import lockfile from "proper-lockfile";
|
|
1319
1320
|
import writeFileAtomic2 from "write-file-atomic";
|
|
1321
|
+
|
|
1322
|
+
// ../../packages/tools-local/dist/config-store/skeleton.js
|
|
1323
|
+
init_esm_shims();
|
|
1324
|
+
function emptySkeleton(name = "") {
|
|
1325
|
+
return {
|
|
1326
|
+
version: "1.2",
|
|
1327
|
+
project: {
|
|
1328
|
+
name,
|
|
1329
|
+
languages: [],
|
|
1330
|
+
frameworks: [],
|
|
1331
|
+
subprojects: []
|
|
1332
|
+
},
|
|
1333
|
+
tools: {
|
|
1334
|
+
confirmed: [],
|
|
1335
|
+
pending_evaluation: [],
|
|
1336
|
+
unknown_in_graph: []
|
|
1337
|
+
},
|
|
1338
|
+
last_audit_entry: null
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// ../../packages/tools-local/dist/config-store/audit.js
|
|
1320
1343
|
var logger4 = (0, import_errors6.createMcpLogger)({ name: "@toolcairn/tools:audit-log" });
|
|
1321
|
-
var MAX_LIVE_ENTRIES =
|
|
1322
|
-
var ARCHIVE_BATCH =
|
|
1344
|
+
var MAX_LIVE_ENTRIES = 5e3;
|
|
1345
|
+
var ARCHIVE_BATCH = 2500;
|
|
1323
1346
|
async function appendAudit(projectRoot, entry) {
|
|
1324
1347
|
await mkdir3(joinConfigDir(projectRoot), { recursive: true });
|
|
1325
1348
|
const auditPath = joinAuditPath(projectRoot);
|
|
@@ -1338,6 +1361,49 @@ async function bulkAppendAudit(projectRoot, entries) {
|
|
|
1338
1361
|
await appendFile(auditPath, payload, "utf-8");
|
|
1339
1362
|
await rotateIfNeeded(projectRoot, auditPath);
|
|
1340
1363
|
}
|
|
1364
|
+
async function appendToolCallAudit(projectRoot, entry) {
|
|
1365
|
+
try {
|
|
1366
|
+
await ensureLockable(projectRoot);
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
logger4.debug({ err, projectRoot }, "audit-log: bootstrap skipped (read-only?) \u2014 abandoning");
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1372
|
+
let release = null;
|
|
1373
|
+
try {
|
|
1374
|
+
release = await lockfile.lock(configPath, {
|
|
1375
|
+
stale: 1e4,
|
|
1376
|
+
retries: { retries: 5, minTimeout: 50, factor: 2, maxTimeout: 500 },
|
|
1377
|
+
realpath: false
|
|
1378
|
+
});
|
|
1379
|
+
await appendAudit(projectRoot, entry);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
logger4.warn({ err, projectRoot }, "audit-log: tool-call append failed");
|
|
1382
|
+
} finally {
|
|
1383
|
+
if (release) {
|
|
1384
|
+
try {
|
|
1385
|
+
await release();
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
logger4.debug({ err }, "audit-log: lock release failed (likely already stale)");
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async function ensureLockable(projectRoot) {
|
|
1393
|
+
await mkdir3(joinConfigDir(projectRoot), { recursive: true });
|
|
1394
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1395
|
+
if (!await fileExists(configPath)) {
|
|
1396
|
+
await writeFile2(configPath, `${JSON.stringify(emptySkeleton(), null, 2)}
|
|
1397
|
+
`, "utf-8");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async function readLiveAudit(projectRoot) {
|
|
1401
|
+
const auditPath = joinAuditPath(projectRoot);
|
|
1402
|
+
if (!await fileExists(auditPath))
|
|
1403
|
+
return [];
|
|
1404
|
+
const raw = await readFile3(auditPath, "utf-8");
|
|
1405
|
+
return parseJsonl(raw);
|
|
1406
|
+
}
|
|
1341
1407
|
async function rotateIfNeeded(projectRoot, auditPath) {
|
|
1342
1408
|
const raw = await readFile3(auditPath, "utf-8");
|
|
1343
1409
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
@@ -1357,6 +1423,18 @@ async function rotateIfNeeded(projectRoot, auditPath) {
|
|
|
1357
1423
|
logger4.warn({ err, auditPath, archivePath }, "Audit-log rotation failed \u2014 live file intact");
|
|
1358
1424
|
}
|
|
1359
1425
|
}
|
|
1426
|
+
function parseJsonl(raw) {
|
|
1427
|
+
const out = [];
|
|
1428
|
+
for (const line of raw.split("\n")) {
|
|
1429
|
+
if (!line.trim())
|
|
1430
|
+
continue;
|
|
1431
|
+
try {
|
|
1432
|
+
out.push(JSON.parse(line));
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return out;
|
|
1437
|
+
}
|
|
1360
1438
|
|
|
1361
1439
|
// ../../packages/tools-local/dist/config-store/migrate.js
|
|
1362
1440
|
init_esm_shims();
|
|
@@ -1429,35 +1507,13 @@ async function migrateToV1_2(config5, projectRoot) {
|
|
|
1429
1507
|
init_esm_shims();
|
|
1430
1508
|
var import_errors7 = __toESM(require_dist2(), 1);
|
|
1431
1509
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
1432
|
-
import
|
|
1433
|
-
|
|
1434
|
-
// ../../packages/tools-local/dist/config-store/skeleton.js
|
|
1435
|
-
init_esm_shims();
|
|
1436
|
-
function emptySkeleton(name = "") {
|
|
1437
|
-
return {
|
|
1438
|
-
version: "1.2",
|
|
1439
|
-
project: {
|
|
1440
|
-
name,
|
|
1441
|
-
languages: [],
|
|
1442
|
-
frameworks: [],
|
|
1443
|
-
subprojects: []
|
|
1444
|
-
},
|
|
1445
|
-
tools: {
|
|
1446
|
-
confirmed: [],
|
|
1447
|
-
pending_evaluation: [],
|
|
1448
|
-
unknown_in_graph: []
|
|
1449
|
-
},
|
|
1450
|
-
last_audit_entry: null
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
// ../../packages/tools-local/dist/config-store/mutate.js
|
|
1510
|
+
import lockfile2 from "proper-lockfile";
|
|
1455
1511
|
var logger5 = (0, import_errors7.createMcpLogger)({ name: "@toolcairn/tools:config-store" });
|
|
1456
1512
|
async function mutateConfig(projectRoot, mutator, audit) {
|
|
1457
1513
|
const configPath = joinConfigPath(projectRoot);
|
|
1458
1514
|
const preExisted = await fileExists(configPath);
|
|
1459
1515
|
await ensureLockableDir(projectRoot);
|
|
1460
|
-
const release = await
|
|
1516
|
+
const release = await lockfile2.lock(configPath, {
|
|
1461
1517
|
stale: 1e4,
|
|
1462
1518
|
retries: { retries: 5, minTimeout: 50, factor: 2, maxTimeout: 500 },
|
|
1463
1519
|
realpath: false
|
|
@@ -4797,6 +4853,35 @@ init_esm_shims();
|
|
|
4797
4853
|
var import_errors22 = __toESM(require_dist2(), 1);
|
|
4798
4854
|
var logger20 = (0, import_errors22.createMcpLogger)({ name: "@toolcairn/tools:read-project-config" });
|
|
4799
4855
|
var STALENESS_THRESHOLD_DAYS = 90;
|
|
4856
|
+
var PENDING_OUTCOME_TTL_DAYS = 7;
|
|
4857
|
+
var RECOMMENDATION_MCP_TOOLS = /* @__PURE__ */ new Set(["search_tools", "search_tools_respond", "get_stack"]);
|
|
4858
|
+
function derivePendingOutcomes(entries) {
|
|
4859
|
+
const cutoff = Date.now() - PENDING_OUTCOME_TTL_DAYS * 24 * 60 * 60 * 1e3;
|
|
4860
|
+
const open = /* @__PURE__ */ new Map();
|
|
4861
|
+
for (const e of entries) {
|
|
4862
|
+
if (e.action !== "tool_call" || !e.query_id)
|
|
4863
|
+
continue;
|
|
4864
|
+
if (e.status === "error")
|
|
4865
|
+
continue;
|
|
4866
|
+
const ts = Date.parse(e.timestamp);
|
|
4867
|
+
if (Number.isNaN(ts) || ts < cutoff)
|
|
4868
|
+
continue;
|
|
4869
|
+
if (e.mcp_tool && RECOMMENDATION_MCP_TOOLS.has(e.mcp_tool)) {
|
|
4870
|
+
const queryFromMeta = typeof e.metadata?.query === "string" ? e.metadata.query : typeof e.metadata?.use_case === "string" ? e.metadata.use_case : null;
|
|
4871
|
+
open.set(e.query_id, {
|
|
4872
|
+
query_id: e.query_id,
|
|
4873
|
+
mcp_tool: e.mcp_tool,
|
|
4874
|
+
selected_at: e.timestamp,
|
|
4875
|
+
age_hours: Math.round((Date.now() - ts) / (1e3 * 60 * 60)),
|
|
4876
|
+
candidates: e.candidates ?? [],
|
|
4877
|
+
query: queryFromMeta
|
|
4878
|
+
});
|
|
4879
|
+
} else if (e.mcp_tool === "report_outcome") {
|
|
4880
|
+
open.delete(e.query_id);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
return Array.from(open.values()).sort((a, b) => Date.parse(a.selected_at) - Date.parse(b.selected_at));
|
|
4884
|
+
}
|
|
4800
4885
|
function daysSince(isoDate) {
|
|
4801
4886
|
return (Date.now() - new Date(isoDate).getTime()) / (1e3 * 60 * 60 * 24);
|
|
4802
4887
|
}
|
|
@@ -4843,6 +4928,13 @@ async function handleReadProjectConfig(args) {
|
|
|
4843
4928
|
});
|
|
4844
4929
|
const non_oss_tools = config5.tools.confirmed.filter((t) => t.source === "non_oss").map((t) => t.name);
|
|
4845
4930
|
const unknown_tools = (config5.tools.unknown_in_graph ?? []).filter((t) => !t.suggested);
|
|
4931
|
+
let pending_outcomes = [];
|
|
4932
|
+
try {
|
|
4933
|
+
const auditEntries = await readLiveAudit(args.project_root);
|
|
4934
|
+
pending_outcomes = derivePendingOutcomes(auditEntries);
|
|
4935
|
+
} catch (err) {
|
|
4936
|
+
logger20.debug({ err }, "pending_outcomes derivation skipped (audit-log unreadable)");
|
|
4937
|
+
}
|
|
4846
4938
|
const toolcairn_indexed_tools = config5.tools.confirmed.filter((t) => t.source === "toolcairn" || t.source === "toolpilot").map((t) => t.name);
|
|
4847
4939
|
const include_locations = args.include_locations === true;
|
|
4848
4940
|
const confirmed_tools_detail = include_locations ? config5.tools.confirmed.map((t) => ({
|
|
@@ -4862,7 +4954,8 @@ async function handleReadProjectConfig(args) {
|
|
|
4862
4954
|
"When recommending tools, skip any already in confirmed_tools.",
|
|
4863
4955
|
non_oss_tools.length > 0 ? `Non-OSS tools in project (handle separately): ${non_oss_tools.join(", ")}` : "",
|
|
4864
4956
|
staleTools.length > 0 ? `Tools that may be stale \u2014 worth re-checking: ${staleTools.map((t) => t.name).join(", ")}` : "",
|
|
4865
|
-
unknown_tools.length > 0 ? `Unknown-to-graph tools awaiting submission (${unknown_tools.length}): ${unknown_tools.map((t) => t.name).join(", ")}. BEFORE answering the user, call suggest_graph_update (suggestion_type="new_tool", data.tools=<this list>) to STAGE them for admin review, then update_project_config action="mark_suggestions_sent" with the successful tool_names.` : ""
|
|
4957
|
+
unknown_tools.length > 0 ? `Unknown-to-graph tools awaiting submission (${unknown_tools.length}): ${unknown_tools.map((t) => t.name).join(", ")}. BEFORE answering the user, call suggest_graph_update (suggestion_type="new_tool", data.tools=<this list>) to STAGE them for admin review, then update_project_config action="mark_suggestions_sent" with the successful tool_names.` : "",
|
|
4958
|
+
pending_outcomes.length > 0 ? `Pending outcomes (${pending_outcomes.length}) \u2014 recommendations the user used but you have not yet reported on. As soon as the user uses (or replaces) one of the suggested tools, call report_outcome with the matching query_id: ${pending_outcomes.map((p) => `${p.query_id} (${p.mcp_tool}, ${p.age_hours}h ago${p.candidates.length > 0 ? `, candidates: ${p.candidates.slice(0, 3).join("/")}` : ""})`).join("; ")}.` : ""
|
|
4866
4959
|
].filter(Boolean);
|
|
4867
4960
|
return okResult({
|
|
4868
4961
|
status: "ready",
|
|
@@ -4883,6 +4976,8 @@ async function handleReadProjectConfig(args) {
|
|
|
4883
4976
|
total_confirmed: confirmedToolNames.length,
|
|
4884
4977
|
total_pending: pendingToolNames.length,
|
|
4885
4978
|
total_unknown_undrained: unknown_tools.length,
|
|
4979
|
+
pending_outcomes,
|
|
4980
|
+
total_pending_outcomes: pending_outcomes.length,
|
|
4886
4981
|
last_audit_entry: config5.last_audit_entry ?? null,
|
|
4887
4982
|
scan_metadata: config5.scan_metadata ?? null,
|
|
4888
4983
|
confirmed_tools_detail,
|
|
@@ -5699,17 +5794,243 @@ async function createIfAbsent(filePath, content, label) {
|
|
|
5699
5794
|
// src/server.prod.ts
|
|
5700
5795
|
init_esm_shims();
|
|
5701
5796
|
var import_config3 = __toESM(require_dist(), 1);
|
|
5702
|
-
var
|
|
5797
|
+
var import_errors28 = __toESM(require_dist2(), 1);
|
|
5703
5798
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5704
5799
|
import { z as z2 } from "zod";
|
|
5705
5800
|
|
|
5801
|
+
// src/middleware/audit-logger.ts
|
|
5802
|
+
init_esm_shims();
|
|
5803
|
+
var import_errors26 = __toESM(require_dist2(), 1);
|
|
5804
|
+
import { existsSync as existsSync2 } from "fs";
|
|
5805
|
+
import { dirname, isAbsolute, join as join33, resolve as resolve4 } from "path";
|
|
5806
|
+
var logger24 = (0, import_errors26.createMcpLogger)({ name: "@toolcairn/mcp-server:audit-logger" });
|
|
5807
|
+
var cwdRootCache = /* @__PURE__ */ new Map();
|
|
5808
|
+
var RECOMMENDATION_TOOLS = /* @__PURE__ */ new Set([
|
|
5809
|
+
"search_tools",
|
|
5810
|
+
"search_tools_respond",
|
|
5811
|
+
"get_stack",
|
|
5812
|
+
"refine_requirement"
|
|
5813
|
+
]);
|
|
5814
|
+
var SKIP_AUDIT = /* @__PURE__ */ new Set([
|
|
5815
|
+
// Currently empty — but reserved if e.g. a heartbeat tool is added later.
|
|
5816
|
+
]);
|
|
5817
|
+
function withAuditLog(toolName, handler) {
|
|
5818
|
+
return async (args) => {
|
|
5819
|
+
const start = Date.now();
|
|
5820
|
+
let result;
|
|
5821
|
+
let status = "ok";
|
|
5822
|
+
let thrown;
|
|
5823
|
+
try {
|
|
5824
|
+
result = await handler(args);
|
|
5825
|
+
if (result.isError) status = "error";
|
|
5826
|
+
} catch (err) {
|
|
5827
|
+
status = "error";
|
|
5828
|
+
thrown = err;
|
|
5829
|
+
}
|
|
5830
|
+
const duration_ms = Date.now() - start;
|
|
5831
|
+
if (result && status === "ok" && RECOMMENDATION_TOOLS.has(toolName)) {
|
|
5832
|
+
result = injectNextActionHint(result, toolName);
|
|
5833
|
+
}
|
|
5834
|
+
if (!SKIP_AUDIT.has(toolName)) {
|
|
5835
|
+
const projectRoot = resolveProjectRoot(args);
|
|
5836
|
+
if (projectRoot) {
|
|
5837
|
+
const entry = buildEntry({
|
|
5838
|
+
toolName,
|
|
5839
|
+
args,
|
|
5840
|
+
result,
|
|
5841
|
+
status,
|
|
5842
|
+
duration_ms
|
|
5843
|
+
});
|
|
5844
|
+
appendToolCallAudit(projectRoot, entry).catch((err) => {
|
|
5845
|
+
logger24.debug({ err, toolName, projectRoot }, "audit-log: append failed (non-fatal)");
|
|
5846
|
+
});
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
if (thrown !== void 0) throw thrown;
|
|
5850
|
+
return result ?? { content: [], isError: true };
|
|
5851
|
+
};
|
|
5852
|
+
}
|
|
5853
|
+
function resolveProjectRoot(args) {
|
|
5854
|
+
const explicit = args.project_root;
|
|
5855
|
+
if (typeof explicit === "string" && explicit.length > 0 && isAbsolute(explicit)) {
|
|
5856
|
+
return explicit;
|
|
5857
|
+
}
|
|
5858
|
+
return findRootForCwd(process.cwd());
|
|
5859
|
+
}
|
|
5860
|
+
function findRootForCwd(cwd) {
|
|
5861
|
+
const cached = cwdRootCache.get(cwd);
|
|
5862
|
+
if (cached !== void 0) return cached;
|
|
5863
|
+
let dir = resolve4(cwd);
|
|
5864
|
+
while (true) {
|
|
5865
|
+
if (existsSync2(join33(dir, ".toolcairn", "config.json"))) {
|
|
5866
|
+
cwdRootCache.set(cwd, dir);
|
|
5867
|
+
return dir;
|
|
5868
|
+
}
|
|
5869
|
+
const parent = dirname(dir);
|
|
5870
|
+
if (parent === dir) {
|
|
5871
|
+
cwdRootCache.set(cwd, null);
|
|
5872
|
+
return null;
|
|
5873
|
+
}
|
|
5874
|
+
dir = parent;
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
function buildEntry(input) {
|
|
5878
|
+
const { toolName, args, result, status, duration_ms } = input;
|
|
5879
|
+
const argQueryId = typeof args.query_id === "string" ? args.query_id : void 0;
|
|
5880
|
+
const data = result ? extractResultData(result) : void 0;
|
|
5881
|
+
const responseQueryId = typeof data?.query_id === "string" ? data.query_id : void 0;
|
|
5882
|
+
const query_id = argQueryId ?? responseQueryId;
|
|
5883
|
+
const tool = pickToolLabel(toolName, args, data);
|
|
5884
|
+
const outcome = pickOutcome(toolName, args);
|
|
5885
|
+
const replaced_by = pickReplacedBy(toolName, args);
|
|
5886
|
+
const candidates = pickCandidates(toolName, data);
|
|
5887
|
+
const metadata = buildMetadata(toolName, args, data);
|
|
5888
|
+
const reason = buildReason(toolName, args, status);
|
|
5889
|
+
const entry = {
|
|
5890
|
+
action: "tool_call",
|
|
5891
|
+
tool,
|
|
5892
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5893
|
+
reason,
|
|
5894
|
+
mcp_tool: toolName,
|
|
5895
|
+
duration_ms,
|
|
5896
|
+
status
|
|
5897
|
+
};
|
|
5898
|
+
if (query_id) entry.query_id = query_id;
|
|
5899
|
+
if (outcome) entry.outcome = outcome;
|
|
5900
|
+
if (replaced_by) entry.replaced_by = replaced_by;
|
|
5901
|
+
if (candidates && candidates.length > 0) entry.candidates = candidates;
|
|
5902
|
+
if (metadata && Object.keys(metadata).length > 0) entry.metadata = metadata;
|
|
5903
|
+
return entry;
|
|
5904
|
+
}
|
|
5905
|
+
function pickToolLabel(toolName, args, data) {
|
|
5906
|
+
if (toolName === "report_outcome" && typeof args.chosen_tool === "string") {
|
|
5907
|
+
return args.chosen_tool;
|
|
5908
|
+
}
|
|
5909
|
+
if ((toolName === "check_compatibility" || toolName === "compare_tools") && typeof args.tool_a === "string" && typeof args.tool_b === "string") {
|
|
5910
|
+
return `${args.tool_a}|${args.tool_b}`;
|
|
5911
|
+
}
|
|
5912
|
+
if (toolName === "check_issue" && typeof args.tool_name === "string") {
|
|
5913
|
+
return args.tool_name;
|
|
5914
|
+
}
|
|
5915
|
+
if (toolName === "verify_suggestion" && Array.isArray(data?.verified)) {
|
|
5916
|
+
return "__verify__";
|
|
5917
|
+
}
|
|
5918
|
+
if (toolName === "update_project_config" && typeof args.tool_name === "string") {
|
|
5919
|
+
return args.tool_name;
|
|
5920
|
+
}
|
|
5921
|
+
return `__call__:${toolName}`;
|
|
5922
|
+
}
|
|
5923
|
+
function pickOutcome(toolName, args) {
|
|
5924
|
+
if (toolName !== "report_outcome") return void 0;
|
|
5925
|
+
const v = args.outcome;
|
|
5926
|
+
if (v === "success" || v === "failure" || v === "replaced" || v === "pending") return v;
|
|
5927
|
+
return void 0;
|
|
5928
|
+
}
|
|
5929
|
+
function pickReplacedBy(toolName, args) {
|
|
5930
|
+
if (toolName !== "report_outcome") return void 0;
|
|
5931
|
+
return typeof args.replaced_by === "string" ? args.replaced_by : void 0;
|
|
5932
|
+
}
|
|
5933
|
+
function pickCandidates(toolName, data) {
|
|
5934
|
+
if (!data) return void 0;
|
|
5935
|
+
if (toolName === "search_tools" || toolName === "search_tools_respond") {
|
|
5936
|
+
const results = data.results;
|
|
5937
|
+
if (Array.isArray(results)) {
|
|
5938
|
+
const names = results.map((r) => r && typeof r === "object" ? r.name : void 0).filter((n) => typeof n === "string").slice(0, 5);
|
|
5939
|
+
return names.length > 0 ? names : void 0;
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
if (toolName === "get_stack") {
|
|
5943
|
+
const stack = data.stack;
|
|
5944
|
+
if (Array.isArray(stack)) {
|
|
5945
|
+
const names = stack.map((r) => r && typeof r === "object" ? r.name : void 0).filter((n) => typeof n === "string").slice(0, 10);
|
|
5946
|
+
return names.length > 0 ? names : void 0;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
return void 0;
|
|
5950
|
+
}
|
|
5951
|
+
function buildMetadata(toolName, args, data) {
|
|
5952
|
+
const meta = {};
|
|
5953
|
+
if (typeof args.query === "string") meta.query = truncate(args.query, 200);
|
|
5954
|
+
if (typeof args.use_case === "string") meta.use_case = truncate(args.use_case, 200);
|
|
5955
|
+
if (typeof args.action === "string") meta.config_action = args.action;
|
|
5956
|
+
if (typeof args.agent === "string") meta.agent = args.agent;
|
|
5957
|
+
if (data) {
|
|
5958
|
+
if (typeof data.status === "string") meta.response_status = data.status;
|
|
5959
|
+
if (typeof data.stage === "number") meta.stage = data.stage;
|
|
5960
|
+
if (typeof data.is_two_option === "boolean") meta.is_two_option = data.is_two_option;
|
|
5961
|
+
if (typeof data.compatibility_signal === "string")
|
|
5962
|
+
meta.compatibility_signal = data.compatibility_signal;
|
|
5963
|
+
if (typeof data.recommendation === "string") meta.recommendation = data.recommendation;
|
|
5964
|
+
if (typeof data.staged === "number") meta.staged = data.staged;
|
|
5965
|
+
}
|
|
5966
|
+
return meta;
|
|
5967
|
+
}
|
|
5968
|
+
function buildReason(toolName, args, status) {
|
|
5969
|
+
if (status === "error") return `MCP tool ${toolName} failed`;
|
|
5970
|
+
if (toolName === "report_outcome" && typeof args.outcome === "string") {
|
|
5971
|
+
const chosen = typeof args.chosen_tool === "string" ? args.chosen_tool : "?";
|
|
5972
|
+
return `report_outcome: ${chosen} \u2192 ${args.outcome}`;
|
|
5973
|
+
}
|
|
5974
|
+
if (toolName === "search_tools" && typeof args.query === "string") {
|
|
5975
|
+
return `search_tools: ${truncate(args.query, 120)}`;
|
|
5976
|
+
}
|
|
5977
|
+
if (toolName === "get_stack" && typeof args.use_case === "string") {
|
|
5978
|
+
return `get_stack: ${truncate(args.use_case, 120)}`;
|
|
5979
|
+
}
|
|
5980
|
+
if (toolName === "update_project_config" && typeof args.action === "string") {
|
|
5981
|
+
const tn = typeof args.tool_name === "string" ? ` ${args.tool_name}` : "";
|
|
5982
|
+
return `update_project_config: ${args.action}${tn}`;
|
|
5983
|
+
}
|
|
5984
|
+
return `MCP tool ${toolName}`;
|
|
5985
|
+
}
|
|
5986
|
+
function truncate(s, n) {
|
|
5987
|
+
return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
|
|
5988
|
+
}
|
|
5989
|
+
function injectNextActionHint(result, toolName) {
|
|
5990
|
+
try {
|
|
5991
|
+
const first = result.content?.[0];
|
|
5992
|
+
if (!first || first.type !== "text") return result;
|
|
5993
|
+
const parsed = JSON.parse(first.text);
|
|
5994
|
+
const data = parsed.data ?? void 0;
|
|
5995
|
+
if (!data) return result;
|
|
5996
|
+
const queryId = typeof data.query_id === "string" ? data.query_id : void 0;
|
|
5997
|
+
if (!queryId) return result;
|
|
5998
|
+
if (typeof data.next_action === "string" && data.next_action.length > 0) return result;
|
|
5999
|
+
data.next_action = buildHint(toolName, queryId);
|
|
6000
|
+
parsed.data = data;
|
|
6001
|
+
return {
|
|
6002
|
+
...result,
|
|
6003
|
+
content: [{ type: "text", text: JSON.stringify(parsed) }]
|
|
6004
|
+
};
|
|
6005
|
+
} catch {
|
|
6006
|
+
return result;
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
function buildHint(toolName, queryId) {
|
|
6010
|
+
if (toolName === "refine_requirement") {
|
|
6011
|
+
return `Use the decomposition to call get_stack or search_tools (passing query_id="${queryId}"); after the user actually uses the chosen tool, call report_outcome({ query_id: "${queryId}", chosen_tool, outcome }).`;
|
|
6012
|
+
}
|
|
6013
|
+
return `After the user actually uses one of the suggested tools (or replaces it), call report_outcome({ query_id: "${queryId}", chosen_tool, outcome }) to close the feedback loop.`;
|
|
6014
|
+
}
|
|
6015
|
+
function extractResultData(result) {
|
|
6016
|
+
try {
|
|
6017
|
+
const first = result.content?.[0];
|
|
6018
|
+
if (!first || first.type !== "text") return void 0;
|
|
6019
|
+
const parsed = JSON.parse(first.text);
|
|
6020
|
+
const data = parsed.data;
|
|
6021
|
+
return data && typeof data === "object" ? data : void 0;
|
|
6022
|
+
} catch {
|
|
6023
|
+
return void 0;
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
|
|
5706
6027
|
// src/middleware/event-logger.ts
|
|
5707
6028
|
init_esm_shims();
|
|
5708
6029
|
var import_config2 = __toESM(require_dist(), 1);
|
|
5709
|
-
var
|
|
6030
|
+
var import_errors27 = __toESM(require_dist2(), 1);
|
|
5710
6031
|
import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
|
|
5711
|
-
import { dirname } from "path";
|
|
5712
|
-
var
|
|
6032
|
+
import { dirname as dirname2 } from "path";
|
|
6033
|
+
var logger25 = (0, import_errors27.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
|
|
5713
6034
|
function isTrackingEnabled() {
|
|
5714
6035
|
return process.env.TOOLCAIRN_TRACKING_ENABLED !== "false";
|
|
5715
6036
|
}
|
|
@@ -5749,11 +6070,11 @@ function extractMetadata(toolName, result) {
|
|
|
5749
6070
|
}
|
|
5750
6071
|
async function writeToFile(eventsPath, event) {
|
|
5751
6072
|
try {
|
|
5752
|
-
await mkdir6(
|
|
6073
|
+
await mkdir6(dirname2(eventsPath), { recursive: true });
|
|
5753
6074
|
await appendFile2(eventsPath, `${JSON.stringify(event)}
|
|
5754
6075
|
`, "utf-8");
|
|
5755
6076
|
} catch (e) {
|
|
5756
|
-
|
|
6077
|
+
logger25.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
|
|
5757
6078
|
}
|
|
5758
6079
|
}
|
|
5759
6080
|
async function sendToApi(event) {
|
|
@@ -5775,7 +6096,7 @@ async function sendToApi(event) {
|
|
|
5775
6096
|
})
|
|
5776
6097
|
});
|
|
5777
6098
|
} catch (e) {
|
|
5778
|
-
|
|
6099
|
+
logger25.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
|
|
5779
6100
|
}
|
|
5780
6101
|
}
|
|
5781
6102
|
function withEventLogging(toolName, handler) {
|
|
@@ -5816,7 +6137,7 @@ function withEventLogging(toolName, handler) {
|
|
|
5816
6137
|
}
|
|
5817
6138
|
|
|
5818
6139
|
// src/server.prod.ts
|
|
5819
|
-
var
|
|
6140
|
+
var logger26 = (0, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
|
|
5820
6141
|
var SETUP_INSTRUCTIONS = `
|
|
5821
6142
|
# ToolCairn \u2014 Agent Setup Instructions
|
|
5822
6143
|
|
|
@@ -5900,9 +6221,12 @@ async function addToolsToServer(server) {
|
|
|
5900
6221
|
apiKey: creds.client_id,
|
|
5901
6222
|
accessToken: creds.access_token
|
|
5902
6223
|
});
|
|
5903
|
-
|
|
6224
|
+
logger26.info({ user: creds.user_email }, "Registering production tools");
|
|
5904
6225
|
function wrap(toolName, fn) {
|
|
5905
|
-
return withEventLogging(
|
|
6226
|
+
return withEventLogging(
|
|
6227
|
+
toolName,
|
|
6228
|
+
withAuditLog(toolName, (0, import_errors28.withErrorHandling)(toolName, logger26, fn))
|
|
6229
|
+
);
|
|
5906
6230
|
}
|
|
5907
6231
|
server.registerTool(
|
|
5908
6232
|
"classify_prompt",
|
|
@@ -6040,7 +6364,8 @@ async function addToolsToServer(server) {
|
|
|
6040
6364
|
)
|
|
6041
6365
|
})
|
|
6042
6366
|
},
|
|
6043
|
-
async (
|
|
6367
|
+
wrap("toolcairn_auth", async (rawArgs) => {
|
|
6368
|
+
const action = rawArgs.action;
|
|
6044
6369
|
if (action === "status") {
|
|
6045
6370
|
const c = await loadCredentials();
|
|
6046
6371
|
const isAuth = c !== null && isTokenValid(c);
|
|
@@ -6075,7 +6400,7 @@ async function addToolsToServer(server) {
|
|
|
6075
6400
|
try {
|
|
6076
6401
|
const user = await startDeviceAuth(import_config3.config.TOOLPILOT_API_URL);
|
|
6077
6402
|
const initSummary = await runPostAuthInit({ agent: "claude" }).catch((err) => {
|
|
6078
|
-
|
|
6403
|
+
logger26.warn({ err }, "runPostAuthInit failed post-login \u2014 auth still succeeds");
|
|
6079
6404
|
return null;
|
|
6080
6405
|
});
|
|
6081
6406
|
return {
|
|
@@ -6102,7 +6427,7 @@ async function addToolsToServer(server) {
|
|
|
6102
6427
|
isError: true
|
|
6103
6428
|
};
|
|
6104
6429
|
}
|
|
6105
|
-
}
|
|
6430
|
+
})
|
|
6106
6431
|
);
|
|
6107
6432
|
}
|
|
6108
6433
|
async function buildProdServer() {
|
|
@@ -6130,14 +6455,14 @@ function createTransport() {
|
|
|
6130
6455
|
|
|
6131
6456
|
// src/index.prod.ts
|
|
6132
6457
|
process.env.TOOLPILOT_MODE = "production";
|
|
6133
|
-
var
|
|
6458
|
+
var logger27 = (0, import_errors29.createMcpLogger)({ name: "@toolcairn/mcp-server" });
|
|
6134
6459
|
async function main() {
|
|
6135
6460
|
await ensureProjectSetup();
|
|
6136
6461
|
const creds = await loadCredentials();
|
|
6137
6462
|
const authenticated = creds !== null && isTokenValid(creds);
|
|
6138
6463
|
let server;
|
|
6139
6464
|
if (authenticated) {
|
|
6140
|
-
|
|
6465
|
+
logger27.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
|
|
6141
6466
|
server = await buildProdServer();
|
|
6142
6467
|
} else {
|
|
6143
6468
|
let verificationUri = "https://toolcairn.neurynae.com/signup";
|
|
@@ -6147,15 +6472,15 @@ async function main() {
|
|
|
6147
6472
|
if (pending) {
|
|
6148
6473
|
verificationUri = pending.verification_uri;
|
|
6149
6474
|
userCode = pending.user_code;
|
|
6150
|
-
|
|
6475
|
+
logger27.info({ userCode }, "Resuming pending sign-in");
|
|
6151
6476
|
} else {
|
|
6152
6477
|
const codeData = await requestDeviceCode(import_config5.config.TOOLPILOT_API_URL);
|
|
6153
6478
|
verificationUri = codeData.verification_uri;
|
|
6154
6479
|
userCode = codeData.user_code;
|
|
6155
|
-
|
|
6480
|
+
logger27.info({ userCode }, "New sign-in started");
|
|
6156
6481
|
}
|
|
6157
6482
|
} catch (err) {
|
|
6158
|
-
|
|
6483
|
+
logger27.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
|
|
6159
6484
|
}
|
|
6160
6485
|
const instructions = userCode ? `# ToolCairn \u2014 Sign In Required
|
|
6161
6486
|
|
|
@@ -6189,17 +6514,17 @@ After sign-in the server will automatically provision .toolcairn/config.json for
|
|
|
6189
6514
|
})
|
|
6190
6515
|
);
|
|
6191
6516
|
startDeviceAuth(import_config5.config.TOOLPILOT_API_URL).then(async () => {
|
|
6192
|
-
|
|
6517
|
+
logger27.info("Sign-in complete \u2014 adding all tools to running server");
|
|
6193
6518
|
try {
|
|
6194
6519
|
await addToolsToServer(server);
|
|
6195
|
-
|
|
6520
|
+
logger27.info("All ToolCairn tools now available");
|
|
6196
6521
|
} catch (err) {
|
|
6197
|
-
|
|
6522
|
+
logger27.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
|
|
6198
6523
|
return;
|
|
6199
6524
|
}
|
|
6200
6525
|
try {
|
|
6201
6526
|
const summary = await runPostAuthInit({ agent: "claude" });
|
|
6202
|
-
|
|
6527
|
+
logger27.info(
|
|
6203
6528
|
{
|
|
6204
6529
|
roots: summary.roots_discovered.length,
|
|
6205
6530
|
provisioned: summary.projects.length,
|
|
@@ -6208,18 +6533,18 @@ After sign-in the server will automatically provision .toolcairn/config.json for
|
|
|
6208
6533
|
"Post-sign-in auto-init complete"
|
|
6209
6534
|
);
|
|
6210
6535
|
} catch (err) {
|
|
6211
|
-
|
|
6536
|
+
logger27.warn({ err }, "Post-sign-in auto-init failed \u2014 call toolcairn_init manually");
|
|
6212
6537
|
}
|
|
6213
6538
|
}).catch((err) => {
|
|
6214
|
-
|
|
6539
|
+
logger27.error({ err }, "Sign-in failed \u2014 please try again");
|
|
6215
6540
|
});
|
|
6216
6541
|
}
|
|
6217
6542
|
const transport = createTransport();
|
|
6218
6543
|
await server.connect(transport);
|
|
6219
|
-
|
|
6544
|
+
logger27.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
|
|
6220
6545
|
if (authenticated) {
|
|
6221
6546
|
void runPostAuthInit({ agent: "claude" }).then((summary) => {
|
|
6222
|
-
|
|
6547
|
+
logger27.info(
|
|
6223
6548
|
{
|
|
6224
6549
|
roots: summary.roots_discovered.length,
|
|
6225
6550
|
provisioned: summary.projects.length,
|
|
@@ -6228,15 +6553,14 @@ After sign-in the server will automatically provision .toolcairn/config.json for
|
|
|
6228
6553
|
"Background auto-refresh complete"
|
|
6229
6554
|
);
|
|
6230
6555
|
}).catch((err) => {
|
|
6231
|
-
|
|
6556
|
+
logger27.warn({ err }, "Background auto-refresh failed \u2014 config left as-is");
|
|
6232
6557
|
});
|
|
6233
6558
|
}
|
|
6234
6559
|
}
|
|
6235
6560
|
main().catch((error) => {
|
|
6236
|
-
(0,
|
|
6561
|
+
(0, import_errors29.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
|
|
6237
6562
|
{ err: error },
|
|
6238
6563
|
"Failed to start MCP server"
|
|
6239
6564
|
);
|
|
6240
6565
|
process.exit(1);
|
|
6241
6566
|
});
|
|
6242
|
-
//# sourceMappingURL=index.js.map
|