@neurynae/toolcairn-mcp 0.10.11 → 0.10.13

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
@@ -675,6 +675,22 @@ var ToolCairnClient = class {
675
675
  async post(path2, body) {
676
676
  try {
677
677
  const res = await this.rawPost(path2, body);
678
+ if (res.status === 429) {
679
+ const exhausted = res.headers.get("x-toolcairn-limit-exhausted") === "1";
680
+ if (exhausted) {
681
+ try {
682
+ const body429 = await res.clone().json();
683
+ const text = body429.message ?? "ToolCairn daily limit reached. Join the Pro waitlist for 1 month free: https://toolcairn.neurynae.com/waitlist?source=limit_exhausted";
684
+ return {
685
+ content: [{ type: "text", text }]
686
+ // isError=false so the agent treats this as a successful tool
687
+ // response and relays the message to the user instead of
688
+ // retrying or bubbling a generic error.
689
+ };
690
+ } catch {
691
+ }
692
+ }
693
+ }
678
694
  const data = await res.json();
679
695
  if (data && typeof data === "object" && "content" in data) {
680
696
  return data;
@@ -923,7 +939,7 @@ async function pollForToken(apiUrl, deviceCode, intervalSec) {
923
939
  }
924
940
  }
925
941
  function sleep(ms) {
926
- return new Promise((resolve4) => setTimeout(resolve4, ms));
942
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
927
943
  }
928
944
 
929
945
  // src/index.prod.ts
@@ -1300,10 +1316,33 @@ async function writeConfig(projectRoot, config5) {
1300
1316
  init_esm_shims();
1301
1317
  var import_errors6 = __toESM(require_dist2(), 1);
1302
1318
  import { appendFile, mkdir as mkdir3, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/promises";
1319
+ import lockfile from "proper-lockfile";
1303
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
1304
1343
  var logger4 = (0, import_errors6.createMcpLogger)({ name: "@toolcairn/tools:audit-log" });
1305
- var MAX_LIVE_ENTRIES = 1e3;
1306
- var ARCHIVE_BATCH = 500;
1344
+ var MAX_LIVE_ENTRIES = 5e3;
1345
+ var ARCHIVE_BATCH = 2500;
1307
1346
  async function appendAudit(projectRoot, entry) {
1308
1347
  await mkdir3(joinConfigDir(projectRoot), { recursive: true });
1309
1348
  const auditPath = joinAuditPath(projectRoot);
@@ -1322,6 +1361,49 @@ async function bulkAppendAudit(projectRoot, entries) {
1322
1361
  await appendFile(auditPath, payload, "utf-8");
1323
1362
  await rotateIfNeeded(projectRoot, auditPath);
1324
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
+ }
1325
1407
  async function rotateIfNeeded(projectRoot, auditPath) {
1326
1408
  const raw = await readFile3(auditPath, "utf-8");
1327
1409
  const lines = raw.split("\n").filter((l) => l.trim().length > 0);
@@ -1341,6 +1423,18 @@ async function rotateIfNeeded(projectRoot, auditPath) {
1341
1423
  logger4.warn({ err, auditPath, archivePath }, "Audit-log rotation failed \u2014 live file intact");
1342
1424
  }
1343
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
+ }
1344
1438
 
1345
1439
  // ../../packages/tools-local/dist/config-store/migrate.js
1346
1440
  init_esm_shims();
@@ -1413,35 +1507,13 @@ async function migrateToV1_2(config5, projectRoot) {
1413
1507
  init_esm_shims();
1414
1508
  var import_errors7 = __toESM(require_dist2(), 1);
1415
1509
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1416
- import lockfile from "proper-lockfile";
1417
-
1418
- // ../../packages/tools-local/dist/config-store/skeleton.js
1419
- init_esm_shims();
1420
- function emptySkeleton(name = "") {
1421
- return {
1422
- version: "1.2",
1423
- project: {
1424
- name,
1425
- languages: [],
1426
- frameworks: [],
1427
- subprojects: []
1428
- },
1429
- tools: {
1430
- confirmed: [],
1431
- pending_evaluation: [],
1432
- unknown_in_graph: []
1433
- },
1434
- last_audit_entry: null
1435
- };
1436
- }
1437
-
1438
- // ../../packages/tools-local/dist/config-store/mutate.js
1510
+ import lockfile2 from "proper-lockfile";
1439
1511
  var logger5 = (0, import_errors7.createMcpLogger)({ name: "@toolcairn/tools:config-store" });
1440
1512
  async function mutateConfig(projectRoot, mutator, audit) {
1441
1513
  const configPath = joinConfigPath(projectRoot);
1442
1514
  const preExisted = await fileExists(configPath);
1443
1515
  await ensureLockableDir(projectRoot);
1444
- const release = await lockfile.lock(configPath, {
1516
+ const release = await lockfile2.lock(configPath, {
1445
1517
  stale: 1e4,
1446
1518
  retries: { retries: 5, minTimeout: 50, factor: 2, maxTimeout: 500 },
1447
1519
  realpath: false
@@ -4781,6 +4853,39 @@ init_esm_shims();
4781
4853
  var import_errors22 = __toESM(require_dist2(), 1);
4782
4854
  var logger20 = (0, import_errors22.createMcpLogger)({ name: "@toolcairn/tools:read-project-config" });
4783
4855
  var STALENESS_THRESHOLD_DAYS = 90;
4856
+ var PENDING_OUTCOME_TTL_DAYS = 7;
4857
+ var RECOMMENDATION_MCP_TOOLS = /* @__PURE__ */ new Set([
4858
+ "search_tools",
4859
+ "search_tools_respond",
4860
+ "get_stack"
4861
+ ]);
4862
+ function derivePendingOutcomes(entries) {
4863
+ const cutoff = Date.now() - PENDING_OUTCOME_TTL_DAYS * 24 * 60 * 60 * 1e3;
4864
+ const open = /* @__PURE__ */ new Map();
4865
+ for (const e of entries) {
4866
+ if (e.action !== "tool_call" || !e.query_id)
4867
+ continue;
4868
+ if (e.status === "error")
4869
+ continue;
4870
+ const ts = Date.parse(e.timestamp);
4871
+ if (Number.isNaN(ts) || ts < cutoff)
4872
+ continue;
4873
+ if (e.mcp_tool && RECOMMENDATION_MCP_TOOLS.has(e.mcp_tool)) {
4874
+ const queryFromMeta = typeof e.metadata?.query === "string" ? e.metadata.query : typeof e.metadata?.use_case === "string" ? e.metadata.use_case : null;
4875
+ open.set(e.query_id, {
4876
+ query_id: e.query_id,
4877
+ mcp_tool: e.mcp_tool,
4878
+ selected_at: e.timestamp,
4879
+ age_hours: Math.round((Date.now() - ts) / (1e3 * 60 * 60)),
4880
+ candidates: e.candidates ?? [],
4881
+ query: queryFromMeta
4882
+ });
4883
+ } else if (e.mcp_tool === "report_outcome") {
4884
+ open.delete(e.query_id);
4885
+ }
4886
+ }
4887
+ return Array.from(open.values()).sort((a, b) => Date.parse(a.selected_at) - Date.parse(b.selected_at));
4888
+ }
4784
4889
  function daysSince(isoDate) {
4785
4890
  return (Date.now() - new Date(isoDate).getTime()) / (1e3 * 60 * 60 * 24);
4786
4891
  }
@@ -4827,6 +4932,13 @@ async function handleReadProjectConfig(args) {
4827
4932
  });
4828
4933
  const non_oss_tools = config5.tools.confirmed.filter((t) => t.source === "non_oss").map((t) => t.name);
4829
4934
  const unknown_tools = (config5.tools.unknown_in_graph ?? []).filter((t) => !t.suggested);
4935
+ let pending_outcomes = [];
4936
+ try {
4937
+ const auditEntries = await readLiveAudit(args.project_root);
4938
+ pending_outcomes = derivePendingOutcomes(auditEntries);
4939
+ } catch (err) {
4940
+ logger20.debug({ err }, "pending_outcomes derivation skipped (audit-log unreadable)");
4941
+ }
4830
4942
  const toolcairn_indexed_tools = config5.tools.confirmed.filter((t) => t.source === "toolcairn" || t.source === "toolpilot").map((t) => t.name);
4831
4943
  const include_locations = args.include_locations === true;
4832
4944
  const confirmed_tools_detail = include_locations ? config5.tools.confirmed.map((t) => ({
@@ -4846,7 +4958,8 @@ async function handleReadProjectConfig(args) {
4846
4958
  "When recommending tools, skip any already in confirmed_tools.",
4847
4959
  non_oss_tools.length > 0 ? `Non-OSS tools in project (handle separately): ${non_oss_tools.join(", ")}` : "",
4848
4960
  staleTools.length > 0 ? `Tools that may be stale \u2014 worth re-checking: ${staleTools.map((t) => t.name).join(", ")}` : "",
4849
- 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.` : ""
4961
+ 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.` : "",
4962
+ 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("; ")}.` : ""
4850
4963
  ].filter(Boolean);
4851
4964
  return okResult({
4852
4965
  status: "ready",
@@ -4867,6 +4980,8 @@ async function handleReadProjectConfig(args) {
4867
4980
  total_confirmed: confirmedToolNames.length,
4868
4981
  total_pending: pendingToolNames.length,
4869
4982
  total_unknown_undrained: unknown_tools.length,
4983
+ pending_outcomes,
4984
+ total_pending_outcomes: pending_outcomes.length,
4870
4985
  last_audit_entry: config5.last_audit_entry ?? null,
4871
4986
  scan_metadata: config5.scan_metadata ?? null,
4872
4987
  confirmed_tools_detail,
@@ -5683,17 +5798,243 @@ async function createIfAbsent(filePath, content, label) {
5683
5798
  // src/server.prod.ts
5684
5799
  init_esm_shims();
5685
5800
  var import_config3 = __toESM(require_dist(), 1);
5686
- var import_errors27 = __toESM(require_dist2(), 1);
5801
+ var import_errors28 = __toESM(require_dist2(), 1);
5687
5802
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5688
5803
  import { z as z2 } from "zod";
5689
5804
 
5805
+ // src/middleware/audit-logger.ts
5806
+ init_esm_shims();
5807
+ var import_errors26 = __toESM(require_dist2(), 1);
5808
+ import { existsSync as existsSync2 } from "fs";
5809
+ import { dirname, isAbsolute, join as join33, resolve as resolve4 } from "path";
5810
+ var logger24 = (0, import_errors26.createMcpLogger)({ name: "@toolcairn/mcp-server:audit-logger" });
5811
+ var cwdRootCache = /* @__PURE__ */ new Map();
5812
+ var RECOMMENDATION_TOOLS = /* @__PURE__ */ new Set([
5813
+ "search_tools",
5814
+ "search_tools_respond",
5815
+ "get_stack",
5816
+ "refine_requirement"
5817
+ ]);
5818
+ var SKIP_AUDIT = /* @__PURE__ */ new Set([
5819
+ // Currently empty — but reserved if e.g. a heartbeat tool is added later.
5820
+ ]);
5821
+ function withAuditLog(toolName, handler) {
5822
+ return async (args) => {
5823
+ const start = Date.now();
5824
+ let result;
5825
+ let status = "ok";
5826
+ let thrown;
5827
+ try {
5828
+ result = await handler(args);
5829
+ if (result.isError) status = "error";
5830
+ } catch (err) {
5831
+ status = "error";
5832
+ thrown = err;
5833
+ }
5834
+ const duration_ms = Date.now() - start;
5835
+ if (result && status === "ok" && RECOMMENDATION_TOOLS.has(toolName)) {
5836
+ result = injectNextActionHint(result, toolName);
5837
+ }
5838
+ if (!SKIP_AUDIT.has(toolName)) {
5839
+ const projectRoot = resolveProjectRoot(args);
5840
+ if (projectRoot) {
5841
+ const entry = buildEntry({
5842
+ toolName,
5843
+ args,
5844
+ result,
5845
+ status,
5846
+ duration_ms
5847
+ });
5848
+ appendToolCallAudit(projectRoot, entry).catch((err) => {
5849
+ logger24.debug({ err, toolName, projectRoot }, "audit-log: append failed (non-fatal)");
5850
+ });
5851
+ }
5852
+ }
5853
+ if (thrown !== void 0) throw thrown;
5854
+ return result ?? { content: [], isError: true };
5855
+ };
5856
+ }
5857
+ function resolveProjectRoot(args) {
5858
+ const explicit = args.project_root;
5859
+ if (typeof explicit === "string" && explicit.length > 0 && isAbsolute(explicit)) {
5860
+ return explicit;
5861
+ }
5862
+ return findRootForCwd(process.cwd());
5863
+ }
5864
+ function findRootForCwd(cwd) {
5865
+ const cached = cwdRootCache.get(cwd);
5866
+ if (cached !== void 0) return cached;
5867
+ let dir = resolve4(cwd);
5868
+ while (true) {
5869
+ if (existsSync2(join33(dir, ".toolcairn", "config.json"))) {
5870
+ cwdRootCache.set(cwd, dir);
5871
+ return dir;
5872
+ }
5873
+ const parent = dirname(dir);
5874
+ if (parent === dir) {
5875
+ cwdRootCache.set(cwd, null);
5876
+ return null;
5877
+ }
5878
+ dir = parent;
5879
+ }
5880
+ }
5881
+ function buildEntry(input) {
5882
+ const { toolName, args, result, status, duration_ms } = input;
5883
+ const argQueryId = typeof args.query_id === "string" ? args.query_id : void 0;
5884
+ const data = result ? extractResultData(result) : void 0;
5885
+ const responseQueryId = typeof data?.query_id === "string" ? data.query_id : void 0;
5886
+ const query_id = argQueryId ?? responseQueryId;
5887
+ const tool = pickToolLabel(toolName, args, data);
5888
+ const outcome = pickOutcome(toolName, args);
5889
+ const replaced_by = pickReplacedBy(toolName, args);
5890
+ const candidates = pickCandidates(toolName, data);
5891
+ const metadata = buildMetadata(toolName, args, data);
5892
+ const reason = buildReason(toolName, args, status);
5893
+ const entry = {
5894
+ action: "tool_call",
5895
+ tool,
5896
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5897
+ reason,
5898
+ mcp_tool: toolName,
5899
+ duration_ms,
5900
+ status
5901
+ };
5902
+ if (query_id) entry.query_id = query_id;
5903
+ if (outcome) entry.outcome = outcome;
5904
+ if (replaced_by) entry.replaced_by = replaced_by;
5905
+ if (candidates && candidates.length > 0) entry.candidates = candidates;
5906
+ if (metadata && Object.keys(metadata).length > 0) entry.metadata = metadata;
5907
+ return entry;
5908
+ }
5909
+ function pickToolLabel(toolName, args, data) {
5910
+ if (toolName === "report_outcome" && typeof args.chosen_tool === "string") {
5911
+ return args.chosen_tool;
5912
+ }
5913
+ if ((toolName === "check_compatibility" || toolName === "compare_tools") && typeof args.tool_a === "string" && typeof args.tool_b === "string") {
5914
+ return `${args.tool_a}|${args.tool_b}`;
5915
+ }
5916
+ if (toolName === "check_issue" && typeof args.tool_name === "string") {
5917
+ return args.tool_name;
5918
+ }
5919
+ if (toolName === "verify_suggestion" && Array.isArray(data?.verified)) {
5920
+ return "__verify__";
5921
+ }
5922
+ if (toolName === "update_project_config" && typeof args.tool_name === "string") {
5923
+ return args.tool_name;
5924
+ }
5925
+ return `__call__:${toolName}`;
5926
+ }
5927
+ function pickOutcome(toolName, args) {
5928
+ if (toolName !== "report_outcome") return void 0;
5929
+ const v = args.outcome;
5930
+ if (v === "success" || v === "failure" || v === "replaced" || v === "pending") return v;
5931
+ return void 0;
5932
+ }
5933
+ function pickReplacedBy(toolName, args) {
5934
+ if (toolName !== "report_outcome") return void 0;
5935
+ return typeof args.replaced_by === "string" ? args.replaced_by : void 0;
5936
+ }
5937
+ function pickCandidates(toolName, data) {
5938
+ if (!data) return void 0;
5939
+ if (toolName === "search_tools" || toolName === "search_tools_respond") {
5940
+ const results = data.results;
5941
+ if (Array.isArray(results)) {
5942
+ const names = results.map((r) => r && typeof r === "object" ? r.name : void 0).filter((n) => typeof n === "string").slice(0, 5);
5943
+ return names.length > 0 ? names : void 0;
5944
+ }
5945
+ }
5946
+ if (toolName === "get_stack") {
5947
+ const stack = data.stack;
5948
+ if (Array.isArray(stack)) {
5949
+ const names = stack.map((r) => r && typeof r === "object" ? r.name : void 0).filter((n) => typeof n === "string").slice(0, 10);
5950
+ return names.length > 0 ? names : void 0;
5951
+ }
5952
+ }
5953
+ return void 0;
5954
+ }
5955
+ function buildMetadata(toolName, args, data) {
5956
+ const meta = {};
5957
+ if (typeof args.query === "string") meta.query = truncate(args.query, 200);
5958
+ if (typeof args.use_case === "string") meta.use_case = truncate(args.use_case, 200);
5959
+ if (typeof args.action === "string") meta.config_action = args.action;
5960
+ if (typeof args.agent === "string") meta.agent = args.agent;
5961
+ if (data) {
5962
+ if (typeof data.status === "string") meta.response_status = data.status;
5963
+ if (typeof data.stage === "number") meta.stage = data.stage;
5964
+ if (typeof data.is_two_option === "boolean") meta.is_two_option = data.is_two_option;
5965
+ if (typeof data.compatibility_signal === "string")
5966
+ meta.compatibility_signal = data.compatibility_signal;
5967
+ if (typeof data.recommendation === "string") meta.recommendation = data.recommendation;
5968
+ if (typeof data.staged === "number") meta.staged = data.staged;
5969
+ }
5970
+ return meta;
5971
+ }
5972
+ function buildReason(toolName, args, status) {
5973
+ if (status === "error") return `MCP tool ${toolName} failed`;
5974
+ if (toolName === "report_outcome" && typeof args.outcome === "string") {
5975
+ const chosen = typeof args.chosen_tool === "string" ? args.chosen_tool : "?";
5976
+ return `report_outcome: ${chosen} \u2192 ${args.outcome}`;
5977
+ }
5978
+ if (toolName === "search_tools" && typeof args.query === "string") {
5979
+ return `search_tools: ${truncate(args.query, 120)}`;
5980
+ }
5981
+ if (toolName === "get_stack" && typeof args.use_case === "string") {
5982
+ return `get_stack: ${truncate(args.use_case, 120)}`;
5983
+ }
5984
+ if (toolName === "update_project_config" && typeof args.action === "string") {
5985
+ const tn = typeof args.tool_name === "string" ? ` ${args.tool_name}` : "";
5986
+ return `update_project_config: ${args.action}${tn}`;
5987
+ }
5988
+ return `MCP tool ${toolName}`;
5989
+ }
5990
+ function truncate(s, n) {
5991
+ return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
5992
+ }
5993
+ function injectNextActionHint(result, toolName) {
5994
+ try {
5995
+ const first = result.content?.[0];
5996
+ if (!first || first.type !== "text") return result;
5997
+ const parsed = JSON.parse(first.text);
5998
+ const data = parsed.data ?? void 0;
5999
+ if (!data) return result;
6000
+ const queryId = typeof data.query_id === "string" ? data.query_id : void 0;
6001
+ if (!queryId) return result;
6002
+ if (typeof data.next_action === "string" && data.next_action.length > 0) return result;
6003
+ data.next_action = buildHint(toolName, queryId);
6004
+ parsed.data = data;
6005
+ return {
6006
+ ...result,
6007
+ content: [{ type: "text", text: JSON.stringify(parsed) }]
6008
+ };
6009
+ } catch {
6010
+ return result;
6011
+ }
6012
+ }
6013
+ function buildHint(toolName, queryId) {
6014
+ if (toolName === "refine_requirement") {
6015
+ 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 }).`;
6016
+ }
6017
+ 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.`;
6018
+ }
6019
+ function extractResultData(result) {
6020
+ try {
6021
+ const first = result.content?.[0];
6022
+ if (!first || first.type !== "text") return void 0;
6023
+ const parsed = JSON.parse(first.text);
6024
+ const data = parsed.data;
6025
+ return data && typeof data === "object" ? data : void 0;
6026
+ } catch {
6027
+ return void 0;
6028
+ }
6029
+ }
6030
+
5690
6031
  // src/middleware/event-logger.ts
5691
6032
  init_esm_shims();
5692
6033
  var import_config2 = __toESM(require_dist(), 1);
5693
- var import_errors26 = __toESM(require_dist2(), 1);
6034
+ var import_errors27 = __toESM(require_dist2(), 1);
5694
6035
  import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
5695
- import { dirname } from "path";
5696
- var logger24 = (0, import_errors26.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
6036
+ import { dirname as dirname2 } from "path";
6037
+ var logger25 = (0, import_errors27.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
5697
6038
  function isTrackingEnabled() {
5698
6039
  return process.env.TOOLCAIRN_TRACKING_ENABLED !== "false";
5699
6040
  }
@@ -5733,11 +6074,11 @@ function extractMetadata(toolName, result) {
5733
6074
  }
5734
6075
  async function writeToFile(eventsPath, event) {
5735
6076
  try {
5736
- await mkdir6(dirname(eventsPath), { recursive: true });
6077
+ await mkdir6(dirname2(eventsPath), { recursive: true });
5737
6078
  await appendFile2(eventsPath, `${JSON.stringify(event)}
5738
6079
  `, "utf-8");
5739
6080
  } catch (e) {
5740
- logger24.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
6081
+ logger25.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
5741
6082
  }
5742
6083
  }
5743
6084
  async function sendToApi(event) {
@@ -5759,7 +6100,7 @@ async function sendToApi(event) {
5759
6100
  })
5760
6101
  });
5761
6102
  } catch (e) {
5762
- logger24.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
6103
+ logger25.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
5763
6104
  }
5764
6105
  }
5765
6106
  function withEventLogging(toolName, handler) {
@@ -5800,7 +6141,7 @@ function withEventLogging(toolName, handler) {
5800
6141
  }
5801
6142
 
5802
6143
  // src/server.prod.ts
5803
- var logger25 = (0, import_errors27.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
6144
+ var logger26 = (0, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
5804
6145
  var SETUP_INSTRUCTIONS = `
5805
6146
  # ToolCairn \u2014 Agent Setup Instructions
5806
6147
 
@@ -5884,9 +6225,12 @@ async function addToolsToServer(server) {
5884
6225
  apiKey: creds.client_id,
5885
6226
  accessToken: creds.access_token
5886
6227
  });
5887
- logger25.info({ user: creds.user_email }, "Registering production tools");
6228
+ logger26.info({ user: creds.user_email }, "Registering production tools");
5888
6229
  function wrap(toolName, fn) {
5889
- return withEventLogging(toolName, (0, import_errors27.withErrorHandling)(toolName, logger25, fn));
6230
+ return withEventLogging(
6231
+ toolName,
6232
+ withAuditLog(toolName, (0, import_errors28.withErrorHandling)(toolName, logger26, fn))
6233
+ );
5890
6234
  }
5891
6235
  server.registerTool(
5892
6236
  "classify_prompt",
@@ -6024,7 +6368,8 @@ async function addToolsToServer(server) {
6024
6368
  )
6025
6369
  })
6026
6370
  },
6027
- async ({ action }) => {
6371
+ wrap("toolcairn_auth", async (rawArgs) => {
6372
+ const action = rawArgs.action;
6028
6373
  if (action === "status") {
6029
6374
  const c = await loadCredentials();
6030
6375
  const isAuth = c !== null && isTokenValid(c);
@@ -6059,7 +6404,7 @@ async function addToolsToServer(server) {
6059
6404
  try {
6060
6405
  const user = await startDeviceAuth(import_config3.config.TOOLPILOT_API_URL);
6061
6406
  const initSummary = await runPostAuthInit({ agent: "claude" }).catch((err) => {
6062
- logger25.warn({ err }, "runPostAuthInit failed post-login \u2014 auth still succeeds");
6407
+ logger26.warn({ err }, "runPostAuthInit failed post-login \u2014 auth still succeeds");
6063
6408
  return null;
6064
6409
  });
6065
6410
  return {
@@ -6086,7 +6431,7 @@ async function addToolsToServer(server) {
6086
6431
  isError: true
6087
6432
  };
6088
6433
  }
6089
- }
6434
+ })
6090
6435
  );
6091
6436
  }
6092
6437
  async function buildProdServer() {
@@ -6114,14 +6459,14 @@ function createTransport() {
6114
6459
 
6115
6460
  // src/index.prod.ts
6116
6461
  process.env.TOOLPILOT_MODE = "production";
6117
- var logger26 = (0, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server" });
6462
+ var logger27 = (0, import_errors29.createMcpLogger)({ name: "@toolcairn/mcp-server" });
6118
6463
  async function main() {
6119
6464
  await ensureProjectSetup();
6120
6465
  const creds = await loadCredentials();
6121
6466
  const authenticated = creds !== null && isTokenValid(creds);
6122
6467
  let server;
6123
6468
  if (authenticated) {
6124
- logger26.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
6469
+ logger27.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
6125
6470
  server = await buildProdServer();
6126
6471
  } else {
6127
6472
  let verificationUri = "https://toolcairn.neurynae.com/signup";
@@ -6131,15 +6476,15 @@ async function main() {
6131
6476
  if (pending) {
6132
6477
  verificationUri = pending.verification_uri;
6133
6478
  userCode = pending.user_code;
6134
- logger26.info({ userCode }, "Resuming pending sign-in");
6479
+ logger27.info({ userCode }, "Resuming pending sign-in");
6135
6480
  } else {
6136
6481
  const codeData = await requestDeviceCode(import_config5.config.TOOLPILOT_API_URL);
6137
6482
  verificationUri = codeData.verification_uri;
6138
6483
  userCode = codeData.user_code;
6139
- logger26.info({ userCode }, "New sign-in started");
6484
+ logger27.info({ userCode }, "New sign-in started");
6140
6485
  }
6141
6486
  } catch (err) {
6142
- logger26.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
6487
+ logger27.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
6143
6488
  }
6144
6489
  const instructions = userCode ? `# ToolCairn \u2014 Sign In Required
6145
6490
 
@@ -6173,17 +6518,17 @@ After sign-in the server will automatically provision .toolcairn/config.json for
6173
6518
  })
6174
6519
  );
6175
6520
  startDeviceAuth(import_config5.config.TOOLPILOT_API_URL).then(async () => {
6176
- logger26.info("Sign-in complete \u2014 adding all tools to running server");
6521
+ logger27.info("Sign-in complete \u2014 adding all tools to running server");
6177
6522
  try {
6178
6523
  await addToolsToServer(server);
6179
- logger26.info("All ToolCairn tools now available");
6524
+ logger27.info("All ToolCairn tools now available");
6180
6525
  } catch (err) {
6181
- logger26.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
6526
+ logger27.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
6182
6527
  return;
6183
6528
  }
6184
6529
  try {
6185
6530
  const summary = await runPostAuthInit({ agent: "claude" });
6186
- logger26.info(
6531
+ logger27.info(
6187
6532
  {
6188
6533
  roots: summary.roots_discovered.length,
6189
6534
  provisioned: summary.projects.length,
@@ -6192,18 +6537,18 @@ After sign-in the server will automatically provision .toolcairn/config.json for
6192
6537
  "Post-sign-in auto-init complete"
6193
6538
  );
6194
6539
  } catch (err) {
6195
- logger26.warn({ err }, "Post-sign-in auto-init failed \u2014 call toolcairn_init manually");
6540
+ logger27.warn({ err }, "Post-sign-in auto-init failed \u2014 call toolcairn_init manually");
6196
6541
  }
6197
6542
  }).catch((err) => {
6198
- logger26.error({ err }, "Sign-in failed \u2014 please try again");
6543
+ logger27.error({ err }, "Sign-in failed \u2014 please try again");
6199
6544
  });
6200
6545
  }
6201
6546
  const transport = createTransport();
6202
6547
  await server.connect(transport);
6203
- logger26.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
6548
+ logger27.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
6204
6549
  if (authenticated) {
6205
6550
  void runPostAuthInit({ agent: "claude" }).then((summary) => {
6206
- logger26.info(
6551
+ logger27.info(
6207
6552
  {
6208
6553
  roots: summary.roots_discovered.length,
6209
6554
  provisioned: summary.projects.length,
@@ -6212,12 +6557,12 @@ After sign-in the server will automatically provision .toolcairn/config.json for
6212
6557
  "Background auto-refresh complete"
6213
6558
  );
6214
6559
  }).catch((err) => {
6215
- logger26.warn({ err }, "Background auto-refresh failed \u2014 config left as-is");
6560
+ logger27.warn({ err }, "Background auto-refresh failed \u2014 config left as-is");
6216
6561
  });
6217
6562
  }
6218
6563
  }
6219
6564
  main().catch((error) => {
6220
- (0, import_errors28.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
6565
+ (0, import_errors29.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
6221
6566
  { err: error },
6222
6567
  "Failed to start MCP server"
6223
6568
  );