@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 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 = createMcpLogger27;
340
- exports.createLogger = createMcpLogger27;
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 createMcpLogger27(opts) {
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, logger27, handler) {
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
- logger27[logLevel]({ err, tool: toolName }, `Tool ${toolName} failed: ${err.message}`);
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
- logger27.error({ err, tool: toolName }, `Unexpected error in tool ${toolName}`);
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 import_errors28 = __toESM(require_dist2(), 1);
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((resolve4) => setTimeout(resolve4, ms));
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 = 1e3;
1322
- var ARCHIVE_BATCH = 500;
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 lockfile from "proper-lockfile";
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 lockfile.lock(configPath, {
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 import_errors27 = __toESM(require_dist2(), 1);
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 import_errors26 = __toESM(require_dist2(), 1);
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 logger24 = (0, import_errors26.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
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(dirname(eventsPath), { recursive: true });
6073
+ await mkdir6(dirname2(eventsPath), { recursive: true });
5753
6074
  await appendFile2(eventsPath, `${JSON.stringify(event)}
5754
6075
  `, "utf-8");
5755
6076
  } catch (e) {
5756
- logger24.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
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
- logger24.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
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 logger25 = (0, import_errors27.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
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
- logger25.info({ user: creds.user_email }, "Registering production tools");
6224
+ logger26.info({ user: creds.user_email }, "Registering production tools");
5904
6225
  function wrap(toolName, fn) {
5905
- return withEventLogging(toolName, (0, import_errors27.withErrorHandling)(toolName, logger25, fn));
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 ({ action }) => {
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
- logger25.warn({ err }, "runPostAuthInit failed post-login \u2014 auth still succeeds");
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 logger26 = (0, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server" });
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
- logger26.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
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
- logger26.info({ userCode }, "Resuming pending sign-in");
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
- logger26.info({ userCode }, "New sign-in started");
6480
+ logger27.info({ userCode }, "New sign-in started");
6156
6481
  }
6157
6482
  } catch (err) {
6158
- logger26.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
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
- logger26.info("Sign-in complete \u2014 adding all tools to running server");
6517
+ logger27.info("Sign-in complete \u2014 adding all tools to running server");
6193
6518
  try {
6194
6519
  await addToolsToServer(server);
6195
- logger26.info("All ToolCairn tools now available");
6520
+ logger27.info("All ToolCairn tools now available");
6196
6521
  } catch (err) {
6197
- logger26.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
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
- logger26.info(
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
- logger26.warn({ err }, "Post-sign-in auto-init failed \u2014 call toolcairn_init manually");
6536
+ logger27.warn({ err }, "Post-sign-in auto-init failed \u2014 call toolcairn_init manually");
6212
6537
  }
6213
6538
  }).catch((err) => {
6214
- logger26.error({ err }, "Sign-in failed \u2014 please try again");
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
- logger26.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
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
- logger26.info(
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
- logger26.warn({ err }, "Background auto-refresh failed \u2014 config left as-is");
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, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
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