grepmax 0.26.44 → 0.26.46

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.
@@ -69,8 +69,8 @@ exports.formatMcpSurprisingConnections = formatMcpSurprisingConnections;
69
69
  exports.mcpLogQuery = mcpLogQuery;
70
70
  const fs = __importStar(require("node:fs"));
71
71
  const path = __importStar(require("node:path"));
72
- const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
73
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
72
+ const server_1 = require("@modelcontextprotocol/server");
73
+ const stdio_1 = require("@modelcontextprotocol/server/stdio");
74
74
  const commander_1 = require("commander");
75
75
  const zod_1 = require("zod");
76
76
  const config_1 = require("../config");
@@ -85,6 +85,7 @@ const index_config_1 = require("../lib/index/index-config");
85
85
  const syncer_1 = require("../lib/index/syncer");
86
86
  const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
87
87
  const index_state_footer_1 = require("../lib/output/index-state-footer");
88
+ const mcp_results_1 = require("../lib/output/mcp-results");
88
89
  const searcher_1 = require("../lib/search/searcher");
89
90
  const annotator_1 = require("../lib/skeleton/annotator");
90
91
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
@@ -123,8 +124,8 @@ function toStringArray(val) {
123
124
  }
124
125
  return [];
125
126
  }
126
- function ok(text) {
127
- return { content: [{ type: "text", text }] };
127
+ function ok(text, structuredContent) {
128
+ return Object.assign({ content: [{ type: "text", text }] }, (structuredContent ? { structuredContent } : {}));
128
129
  }
129
130
  function err(text) {
130
131
  return { content: [{ type: "text", text }], isError: true };
@@ -256,13 +257,13 @@ function searchResultPath(r) {
256
257
  }
257
258
  function searchResultStartLine(r) {
258
259
  var _a;
259
- var _b, _c;
260
- return Number((_c = (_b = r === null || r === void 0 ? void 0 : r.start_line) !== null && _b !== void 0 ? _b : (_a = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _a === void 0 ? void 0 : _a.start_line) !== null && _c !== void 0 ? _c : 0);
260
+ var _b, _c, _d;
261
+ return Number((_d = (_c = (_b = r === null || r === void 0 ? void 0 : r.startLine) !== null && _b !== void 0 ? _b : r === null || r === void 0 ? void 0 : r.start_line) !== null && _c !== void 0 ? _c : (_a = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _a === void 0 ? void 0 : _a.start_line) !== null && _d !== void 0 ? _d : 0);
261
262
  }
262
263
  function searchResultEndLine(r, fallbackStart = 0) {
263
264
  var _a;
264
- var _b, _c;
265
- return Number((_c = (_b = r === null || r === void 0 ? void 0 : r.end_line) !== null && _b !== void 0 ? _b : (_a = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _a === void 0 ? void 0 : _a.end_line) !== null && _c !== void 0 ? _c : fallbackStart);
265
+ var _b, _c, _d;
266
+ return Number((_d = (_c = (_b = r === null || r === void 0 ? void 0 : r.endLine) !== null && _b !== void 0 ? _b : r === null || r === void 0 ? void 0 : r.end_line) !== null && _c !== void 0 ? _c : (_a = r === null || r === void 0 ? void 0 : r.generated_metadata) === null || _a === void 0 ? void 0 : _a.end_line) !== null && _d !== void 0 ? _d : fallbackStart);
266
267
  }
267
268
  function filterMcpSearchResults(data, options = {}) {
268
269
  var _a, _b;
@@ -373,12 +374,22 @@ exports.mcp = new commander_1.Command("mcp")
373
374
  // an MCP session holds no reader marker under ~/.gmax/lancedb.lease/readers/
374
375
  // while a daemon serving the verbs is up.
375
376
  let _skeletonizer = null;
376
- let _indexReady = false;
377
- // Nothing to close: the tools open no store of their own.
378
- const exit = () => process.exit(0);
377
+ let stdio;
378
+ let watchTimer;
379
+ const watchedRoots = new Set();
380
+ let closing = false;
381
+ // Close the SDK connection before exit so pending requests are aborted.
382
+ const exit = () => {
383
+ if (closing)
384
+ return;
385
+ closing = true;
386
+ clearInterval(watchTimer);
387
+ setTimeout(() => process.exit(0), 1000).unref();
388
+ void Promise.resolve(stdio === null || stdio === void 0 ? void 0 : stdio.close()).finally(() => process.exit(0));
389
+ };
379
390
  process.on("SIGINT", exit);
380
391
  process.on("SIGTERM", exit);
381
- // MCP SDK doesn't handle stdin close — exit when the client disconnects
392
+ // Exit promptly on disconnect, including sessions with a lease timer.
382
393
  process.stdin.on("end", exit);
383
394
  process.stdin.on("close", exit);
384
395
  process.on("unhandledRejection", (reason, promise) => {
@@ -394,7 +405,7 @@ exports.mcp = new commander_1.Command("mcp")
394
405
  console.debug = (..._args) => { };
395
406
  // --- Project context ---
396
407
  const projectRoot = (0, project_root_1.findProjectRoot)(process.cwd());
397
- const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
408
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot, { dryRun: true });
398
409
  const currentRoot = () => { var _a; var _b; return (_b = (_a = (0, store_context_1.currentStoreContext)()) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : projectRoot; };
399
410
  const storeProjects = () => { var _a; var _b; return (_b = (_a = (0, store_context_1.currentStoreContext)()) === null || _a === void 0 ? void 0 : _a.projects) !== null && _b !== void 0 ? _b : (0, project_registry_1.listProjects)(); };
400
411
  const storeConfig = () => { var _a; var _b; return (_b = (_a = (0, store_context_1.currentStoreContext)()) === null || _a === void 0 ? void 0 : _a.config) !== null && _b !== void 0 ? _b : (0, index_config_1.readGlobalConfig)(); };
@@ -597,18 +608,6 @@ exports.mcp = new commander_1.Command("mcp")
597
608
  return _skeletonizer;
598
609
  });
599
610
  }
600
- // --- Index sync ---
601
- function ensureIndexReady() {
602
- return __awaiter(this, void 0, void 0, function* () {
603
- if (_indexReady)
604
- return;
605
- const projects = storeProjects();
606
- const isRegistered = projects.some((p) => p.root === currentRoot());
607
- if (isRegistered) {
608
- _indexReady = true;
609
- }
610
- });
611
- }
612
611
  // --- Background watcher ---
613
612
  // This server's watch lease. It names our PID, so the daemon drops it the
614
613
  // minute this session exits; the TTL only covers a wedged server.
@@ -627,6 +626,17 @@ exports.mcp = new commander_1.Command("mcp")
627
626
  (0, stores_1.matchStore)(watchRoot).kind !== "primary")
628
627
  return;
629
628
  const result = yield (0, watcher_launcher_1.launchWatcher)(watchRoot, WATCH_LEASE);
629
+ if (closing)
630
+ return;
631
+ if (result.ok) {
632
+ watchedRoots.add(watchRoot);
633
+ // Discovery/catalog requests never reach this path. Renew only roots
634
+ // actually read by this session, with one timer for the whole process.
635
+ watchTimer !== null && watchTimer !== void 0 ? watchTimer : (watchTimer = setInterval(() => {
636
+ for (const root of watchedRoots)
637
+ void ensureWatcher(root);
638
+ }, MCP_WATCH_LEASE_RENEW_MS).unref());
639
+ }
630
640
  if (result.ok && !result.reused) {
631
641
  console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
632
642
  }
@@ -772,6 +782,26 @@ exports.mcp = new commander_1.Command("mcp")
772
782
  return (candidate.length > 0 &&
773
783
  allowedRoots.some((root) => (0, path_containment_1.isPathWithin)(root, candidate)));
774
784
  });
785
+ const structuredSearch = (matches) => { var _a; return ({
786
+ schemaVersion: 1,
787
+ query,
788
+ roots: allowedRoots,
789
+ warnings: ((_a = result.warnings) !== null && _a !== void 0 ? _a : []).filter(Boolean),
790
+ matches: matches.map((r) => {
791
+ var _a;
792
+ const startLine = searchResultStartLine(r);
793
+ return {
794
+ path: chunkAbsPath(r),
795
+ startLine: startLine + 1,
796
+ endLine: searchResultEndLine(r, startLine) + 1,
797
+ symbols: chunkSymbols(r),
798
+ role: String((_a = r.role) !== null && _a !== void 0 ? _a : "IMPLEMENTATION"),
799
+ score: typeof r.score === "number" && Number.isFinite(r.score)
800
+ ? r.score
801
+ : null,
802
+ };
803
+ }),
804
+ }); };
775
805
  // Prepend any searcher warnings to whatever body we return.
776
806
  const prefixNotes = (body) => {
777
807
  var _a;
@@ -779,7 +809,7 @@ exports.mcp = new commander_1.Command("mcp")
779
809
  return notes.length ? `${notes.join("\n")}\n\n${body}` : body;
780
810
  };
781
811
  if (!result.data || result.data.length === 0) {
782
- return ok(prefixNotes("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed."));
812
+ return ok(prefixNotes("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed."), structuredSearch([]));
783
813
  }
784
814
  const minScore = typeof args.min_score === "number" ? args.min_score : 0;
785
815
  const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
@@ -809,21 +839,25 @@ exports.mcp = new commander_1.Command("mcp")
809
839
  getImportsForFile,
810
840
  query,
811
841
  });
812
- return ok(prefixNotes(output));
842
+ return ok(prefixNotes(output), structuredSearch(filterMcpSearchResults(result.data, {
843
+ minScore,
844
+ maxPerFile,
845
+ namePattern,
846
+ })));
813
847
  }
814
848
  let results = result.data.map((r) => {
815
- var _a, _b, _c;
816
- var _d, _e, _f, _g, _h, _j, _k, _l, _m;
817
- const absPath = (_e = (_d = r.path) !== null && _d !== void 0 ? _d : (_a = r.metadata) === null || _a === void 0 ? void 0 : _a.path) !== null && _e !== void 0 ? _e : "";
849
+ var _a;
850
+ var _b, _c, _d, _e, _f;
851
+ const absPath = (_c = (_b = r.path) !== null && _b !== void 0 ? _b : (_a = r.metadata) === null || _a === void 0 ? void 0 : _a.path) !== null && _c !== void 0 ? _c : "";
818
852
  const relPath = absPath.startsWith(displayRoot)
819
853
  ? absPath.slice(displayRoot.length + 1)
820
854
  : absPath;
821
- const startLine = (_g = (_f = r.startLine) !== null && _f !== void 0 ? _f : (_b = r.generated_metadata) === null || _b === void 0 ? void 0 : _b.start_line) !== null && _g !== void 0 ? _g : 0;
822
- const endLine = (_j = (_h = r.endLine) !== null && _h !== void 0 ? _h : (_c = r.generated_metadata) === null || _c === void 0 ? void 0 : _c.end_line) !== null && _j !== void 0 ? _j : 0;
823
- const defs = toStringArray((_k = r.definedSymbols) !== null && _k !== void 0 ? _k : r.defined_symbols);
824
- const refs = toStringArray((_l = r.referenced_symbols) !== null && _l !== void 0 ? _l : r.referencedSymbols);
855
+ const startLine = searchResultStartLine(r);
856
+ const endLine = searchResultEndLine(r, startLine);
857
+ const defs = toStringArray((_d = r.definedSymbols) !== null && _d !== void 0 ? _d : r.defined_symbols);
858
+ const refs = toStringArray((_e = r.referenced_symbols) !== null && _e !== void 0 ? _e : r.referencedSymbols);
825
859
  const symbol = defs[0] || "(anonymous)";
826
- const role = ((_m = r.role) !== null && _m !== void 0 ? _m : "IMPL").slice(0, 4).toUpperCase();
860
+ const role = ((_f = r.role) !== null && _f !== void 0 ? _f : "IMPL").slice(0, 4).toUpperCase();
827
861
  const exported = r.is_exported ? "exported " : "";
828
862
  const complexity = typeof r.complexity === "number" && r.complexity > 0
829
863
  ? ` C:${Math.round(r.complexity)}`
@@ -855,7 +889,7 @@ exports.mcp = new commander_1.Command("mcp")
855
889
  .map((l, i) => `${ctxStart + i + 1}│${l}`)
856
890
  .join("\n");
857
891
  }
858
- catch (_o) {
892
+ catch (_g) {
859
893
  // Fall through to chunk content
860
894
  }
861
895
  }
@@ -885,6 +919,7 @@ exports.mcp = new commander_1.Command("mcp")
885
919
  }
886
920
  }
887
921
  return {
922
+ row: r,
888
923
  absPath,
889
924
  text,
890
925
  score: typeof r.score === "number" ? r.score : 0,
@@ -957,7 +992,7 @@ exports.mcp = new commander_1.Command("mcp")
957
992
  // Trace failed — return search results without trace
958
993
  }
959
994
  }
960
- return ok(prefixNotes(output));
995
+ return ok(prefixNotes(output), structuredSearch(results.map((r) => r.row)));
961
996
  }
962
997
  catch (e) {
963
998
  return toolError("Search failed", e);
@@ -1105,8 +1140,9 @@ exports.mcp = new commander_1.Command("mcp")
1105
1140
  render: (resp) => resp.graph,
1106
1141
  inProcess: () => localStore((deps) => (0, graph_handler_1.runGraphTrace)(deps.vectorDb, { symbol, hops: depth, scope })),
1107
1142
  });
1143
+ const structured = (0, mcp_results_1.traceResult)(symbol, root, graph);
1108
1144
  if (!graph.center) {
1109
- return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
1145
+ return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`, structured);
1110
1146
  }
1111
1147
  const lines = [];
1112
1148
  // Center
@@ -1165,7 +1201,7 @@ exports.mcp = new commander_1.Command("mcp")
1165
1201
  else {
1166
1202
  lines.push("Calls: none");
1167
1203
  }
1168
- return ok(lines.join("\n"));
1204
+ return ok(lines.join("\n"), structured);
1169
1205
  }
1170
1206
  catch (e) {
1171
1207
  return toolError("Trace failed", e);
@@ -1406,16 +1442,17 @@ exports.mcp = new commander_1.Command("mcp")
1406
1442
  render: (resp) => resp.dead,
1407
1443
  inProcess: () => localStore((deps) => (0, graph_handler_1.runGraphDead)(deps.vectorDb, { symbol, scope })),
1408
1444
  });
1445
+ const structured = (0, mcp_results_1.deadResult)(symbol, root, facts);
1409
1446
  if (!facts.found) {
1410
- return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
1447
+ return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`, structured);
1411
1448
  }
1412
1449
  const rel = (p) => p.startsWith(root) ? p.slice(root.length + 1) : p;
1413
1450
  const defLoc = `${rel(facts.defPath)}:${facts.defLine + 1}`;
1414
1451
  if (facts.callerCount === 0) {
1415
1452
  if (facts.isExported) {
1416
- return ok(`PUBLIC EXPORT ${defLoc} defines ${symbol} — no internal callers found; check external usage`);
1453
+ return ok(`PUBLIC EXPORT ${defLoc} defines ${symbol} — no internal callers found; check external usage`, structured);
1417
1454
  }
1418
- return ok(`DEAD ${defLoc} defines ${symbol}`);
1455
+ return ok(`DEAD ${defLoc} defines ${symbol}`, structured);
1419
1456
  }
1420
1457
  const top = facts.topCallers;
1421
1458
  const lines = [
@@ -1424,7 +1461,7 @@ exports.mcp = new commander_1.Command("mcp")
1424
1461
  for (const c of top) {
1425
1462
  lines.push(` ${rel(c.file)}:${c.line + 1}`);
1426
1463
  }
1427
- return ok(lines.join("\n"));
1464
+ return ok(lines.join("\n"), structured);
1428
1465
  }
1429
1466
  catch (e) {
1430
1467
  return toolError("Dead check failed", e);
@@ -1782,7 +1819,7 @@ exports.mcp = new commander_1.Command("mcp")
1782
1819
  }
1783
1820
  function handleIndexStatus() {
1784
1821
  return __awaiter(this, void 0, void 0, function* () {
1785
- var _a, _b;
1822
+ var _a, _b, _c, _d, _e;
1786
1823
  try {
1787
1824
  const globalConfig = storeConfig();
1788
1825
  const projects = storeProjects();
@@ -1834,7 +1871,36 @@ exports.mcp = new commander_1.Command("mcp")
1834
1871
  (0, compaction_result_1.formatCompactionStatus)(daemonStatus.compaction),
1835
1872
  `Projects: ${projects.length} indexed (call list_projects for names + per-project chunk counts)`,
1836
1873
  ].filter(Boolean);
1837
- return ok(lines.join("\n"));
1874
+ const compaction = mcp_results_1.compactionSchema.safeParse(daemonStatus.compaction);
1875
+ return ok(lines.join("\n"), {
1876
+ schemaVersion: 1,
1877
+ root: currentRoot(),
1878
+ store: storeDir(),
1879
+ secondary: secondary(),
1880
+ chunks,
1881
+ files,
1882
+ projects: projects.length,
1883
+ embedding: {
1884
+ state: identity.state,
1885
+ configured: {
1886
+ tier: identity.configured.tier,
1887
+ vectorDim: identity.configured.vectorDim,
1888
+ fingerprint: identity.configured.fingerprint,
1889
+ },
1890
+ built: identity.built
1891
+ ? {
1892
+ tier: identity.built.tier,
1893
+ vectorDim: identity.built.vectorDim,
1894
+ fingerprint: identity.built.fingerprint,
1895
+ }
1896
+ : null,
1897
+ },
1898
+ watcher: {
1899
+ status: (_d = (_c = daemonProject === null || daemonProject === void 0 ? void 0 : daemonProject.status) !== null && _c !== void 0 ? _c : watcher === null || watcher === void 0 ? void 0 : watcher.status) !== null && _d !== void 0 ? _d : (secondary() || !daemonStatus.ok ? "unobserved" : "not_running"),
1900
+ indexState: (_e = daemonProject === null || daemonProject === void 0 ? void 0 : daemonProject.indexState) !== null && _e !== void 0 ? _e : null,
1901
+ },
1902
+ compaction: compaction.success ? compaction.data : null,
1903
+ });
1838
1904
  }
1839
1905
  catch (e) {
1840
1906
  return toolError("Status check failed", e);
@@ -2572,510 +2638,544 @@ exports.mcp = new commander_1.Command("mcp")
2572
2638
  });
2573
2639
  }
2574
2640
  // --- MCP server setup ---
2575
- // Local-only MCP: client-owned pipes, no HTTP/SSE/TCP listener or host option.
2576
- const transport = new stdio_js_1.StdioServerTransport();
2577
- const server = new mcp_js_1.McpServer({
2578
- name: "gmax",
2579
- version: JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), {
2580
- encoding: "utf-8",
2581
- })).version,
2582
- }, {
2583
- capabilities: {
2584
- tools: {},
2585
- },
2586
- instructions: "gmax is a local semantic code-search index over one or more projects. " +
2587
- "By default tools operate on the project at the server's working directory. " +
2588
- "If you are NOT inside a specific repo, or want a different one: call " +
2589
- '`list_projects` to see what\'s indexed, then pass `projects:"name"` (or ' +
2590
- '`scope:"all"`) to `semantic_search`. When the working directory isn\'t an ' +
2591
- "indexed project, `semantic_search` errors instead of searching everything " +
2592
- "implicitly. Prefer `semantic_search` (detail:'pointer') for discovery, then " +
2593
- "`extract_symbol`/`peek_symbol`/`code_skeleton` to read, and " +
2594
- "`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions. " +
2595
- "Use `audit` and experimental `surprising_connections` for project orientation.",
2596
- });
2597
- // Best-effort query logging, applied uniformly to every tool exactly as the
2598
- // old single CallToolRequestSchema dispatch did before each return.
2599
- const logToolCall = (name, toolArgs, startMs, result) => __awaiter(void 0, void 0, void 0, function* () {
2600
- var _a, _b;
2601
- var _c;
2602
- try {
2603
- const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
2604
- const text = (_c = (_b = (_a = result.content) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : "";
2605
- const resultLines = text.split("\n").filter((l) => l.trim()).length;
2606
- logQuery({
2607
- ts: new Date().toISOString(),
2608
- source: "mcp",
2609
- tool: name,
2610
- query: mcpLogQuery(name, toolArgs),
2611
- project: currentRoot(),
2612
- results: resultLines,
2613
- ms: Date.now() - startMs,
2614
- error: result.isError ? text.slice(0, 200) : undefined,
2615
- });
2616
- }
2617
- catch (_d) { }
2618
- });
2619
- // Register a tool, wrapping its handler with timing + query logging. Zod raw
2620
- // shapes give us free input validation (the SDK rejects calls that violate
2621
- // the schema before the handler runs); handlers stay defensively coded.
2622
- function tool(name, config, handler) {
2623
- server.registerTool(name, Object.assign(Object.assign({}, config), { inputSchema: Object.assign({ root: zod_1.z.string().optional() }, config.inputSchema) }), (rawArgs) => __awaiter(this, void 0, void 0, function* () {
2624
- let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
2625
- const startMs = Date.now();
2626
- let target = projectRoot;
2641
+ // One fresh catalog per pinned connection or disposable discovery probe.
2642
+ // Building it performs no daemon, model, store or watcher startup.
2643
+ function createServer() {
2644
+ const server = new server_1.McpServer({
2645
+ name: "gmax",
2646
+ version: JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), {
2647
+ encoding: "utf-8",
2648
+ })).version,
2649
+ }, {
2650
+ capabilities: {
2651
+ tools: {},
2652
+ },
2653
+ instructions: "gmax is a local semantic code-search index over one or more projects. " +
2654
+ "By default tools operate on the project at the server's working directory. " +
2655
+ "If you are NOT inside a specific repo, or want a different one: call " +
2656
+ '`list_projects` to see what\'s indexed, then pass `projects:"name"` (or ' +
2657
+ '`scope:"all"`) to `semantic_search`. When the working directory isn\'t an ' +
2658
+ "indexed project, `semantic_search` errors instead of searching everything " +
2659
+ "implicitly. Prefer `semantic_search` (detail:'pointer') for discovery, then " +
2660
+ "`extract_symbol`/`peek_symbol`/`code_skeleton` to read, and " +
2661
+ "`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions. " +
2662
+ "Use `audit` and experimental `surprising_connections` for project orientation.",
2663
+ });
2664
+ // Best-effort query logging, applied uniformly to every tool exactly as the
2665
+ // old single CallToolRequestSchema dispatch did before each return.
2666
+ const logToolCall = (name, toolArgs, startMs, result) => __awaiter(this, void 0, void 0, function* () {
2667
+ var _a, _b;
2668
+ var _c;
2627
2669
  try {
2628
- if (typeof args.root === "string" && args.root.trim()) {
2629
- const raw = args.root.trim();
2630
- if (path.isAbsolute(raw) || raw.includes(path.sep))
2631
- target = path.resolve(raw);
2632
- else {
2633
- const projects = [
2634
- ...(0, project_registry_1.listProjects)(),
2635
- ...(0, store_context_1.storeInventory)().flatMap((store) => store.projects),
2636
- ].filter((project, i, all) => all.findIndex((p) => p.root === project.root) === i);
2637
- const selected = resolveMcpProject(raw, projectRoot, projects);
2638
- if (!selected)
2639
- return err(`Unknown or ambiguous registered project: ${raw}. Use list_projects or an exact root.`);
2640
- target = selected.root;
2670
+ const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
2671
+ const text = (_c = (_b = (_a = result.content) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : "";
2672
+ const resultLines = text.split("\n").filter((l) => l.trim()).length;
2673
+ logQuery({
2674
+ ts: new Date().toISOString(),
2675
+ source: "mcp",
2676
+ tool: name,
2677
+ query: mcpLogQuery(name, toolArgs),
2678
+ project: currentRoot(),
2679
+ results: resultLines,
2680
+ ms: Date.now() - startMs,
2681
+ error: result.isError ? text.slice(0, 200) : undefined,
2682
+ });
2683
+ }
2684
+ catch (_d) { }
2685
+ });
2686
+ // Register a tool, wrapping its handler with timing + query logging. Zod raw
2687
+ // shapes give us free input validation (the SDK rejects calls that violate
2688
+ // the schema before the handler runs); handlers stay defensively coded.
2689
+ function tool(name, config, handler) {
2690
+ server.registerTool(name, Object.assign(Object.assign(Object.assign({}, config), (mcp_results_1.MCP_READ_OUTPUT_SCHEMAS[name]
2691
+ ? {
2692
+ outputSchema: zod_1.z.object(mcp_results_1.MCP_READ_OUTPUT_SCHEMAS[name]),
2693
+ annotations: {
2694
+ readOnlyHint: true,
2695
+ destructiveHint: false,
2696
+ openWorldHint: false,
2697
+ },
2698
+ }
2699
+ : {})), { inputSchema: zod_1.z.object(Object.assign({ root: zod_1.z.string().optional() }, config.inputSchema)) }), (rawArgs) => __awaiter(this, void 0, void 0, function* () {
2700
+ let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
2701
+ const startMs = Date.now();
2702
+ let target = projectRoot;
2703
+ try {
2704
+ if (typeof args.root === "string" && args.root.trim()) {
2705
+ const raw = args.root.trim();
2706
+ if (path.isAbsolute(raw) || raw.includes(path.sep))
2707
+ target = path.resolve(raw);
2708
+ else {
2709
+ const projects = [
2710
+ ...(0, project_registry_1.listProjects)(),
2711
+ ...(0, store_context_1.storeInventory)().flatMap((store) => store.projects),
2712
+ ].filter((project, i, all) => all.findIndex((p) => p.root === project.root) === i);
2713
+ const selected = resolveMcpProject(raw, projectRoot, projects);
2714
+ if (!selected)
2715
+ return err(`Unknown or ambiguous registered project: ${raw}. Use list_projects or an exact root.`);
2716
+ target = selected.root;
2717
+ }
2641
2718
  }
2719
+ return yield (0, store_context_1.withStoreContext)(target, () => __awaiter(this, void 0, void 0, function* () {
2720
+ if (args.root !== undefined) {
2721
+ const selected = resolveMcpProject(args.root, currentRoot(), storeProjects());
2722
+ if (!selected)
2723
+ return err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
2724
+ args = Object.assign(Object.assign({}, args), { root: selected.root });
2725
+ (0, store_context_1.currentStoreContext)().root = selected.root;
2726
+ }
2727
+ const result = yield handler(args);
2728
+ yield logToolCall(name, args, startMs, result);
2729
+ return result;
2730
+ }));
2642
2731
  }
2643
- return yield (0, store_context_1.withStoreContext)(target, () => __awaiter(this, void 0, void 0, function* () {
2644
- if (args.root !== undefined) {
2645
- const selected = resolveMcpProject(args.root, currentRoot(), storeProjects());
2646
- if (!selected)
2647
- return err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
2648
- args = Object.assign(Object.assign({}, args), { root: selected.root });
2649
- (0, store_context_1.currentStoreContext)().root = selected.root;
2650
- }
2651
- const result = yield handler(args);
2652
- yield logToolCall(name, args, startMs, result);
2653
- return result;
2654
- }));
2655
- }
2656
- catch (error) {
2657
- return toolError("Tool failed", error);
2658
- }
2659
- }));
2732
+ catch (error) {
2733
+ return toolError("Tool failed", error);
2734
+ }
2735
+ }));
2736
+ }
2737
+ tool("semantic_search", {
2738
+ description: "Search code by meaning. Use scope:'all' for cross-project. Prefer CLI: gmax \"query\" --plain",
2739
+ inputSchema: {
2740
+ query: zod_1.z
2741
+ .string()
2742
+ .describe("Natural language query (5+ words recommended)"),
2743
+ limit: zod_1.z
2744
+ .number()
2745
+ .describe("Max results (default 3, max 50)")
2746
+ .optional(),
2747
+ root: zod_1.z
2748
+ .string()
2749
+ .describe("Registered project name or exact root")
2750
+ .optional(),
2751
+ path: zod_1.z
2752
+ .string()
2753
+ .describe("Path prefix filter (e.g. 'src/auth/')")
2754
+ .optional(),
2755
+ detail: zod_1.z
2756
+ .string()
2757
+ .describe("'pointer' (default), 'code', or 'full'")
2758
+ .optional(),
2759
+ min_score: zod_1.z
2760
+ .number()
2761
+ .describe("Min score 0-1 (default 0)")
2762
+ .optional(),
2763
+ max_per_file: zod_1.z
2764
+ .number()
2765
+ .describe("Max results per file")
2766
+ .optional(),
2767
+ file: zod_1.z
2768
+ .string()
2769
+ .describe("Filename filter (e.g. 'syncer.ts')")
2770
+ .optional(),
2771
+ exclude: zod_1.z
2772
+ .string()
2773
+ .describe("Exclude path prefix (e.g. 'tests/')")
2774
+ .optional(),
2775
+ language: zod_1.z
2776
+ .string()
2777
+ .describe("Extension filter (e.g. 'ts', 'py')")
2778
+ .optional(),
2779
+ role: zod_1.z
2780
+ .string()
2781
+ .describe("'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'")
2782
+ .optional(),
2783
+ context_lines: zod_1.z
2784
+ .number()
2785
+ .describe("Lines before/after chunk (max 20)")
2786
+ .optional(),
2787
+ mode: zod_1.z
2788
+ .string()
2789
+ .describe("'default' or 'symbol' (appends call graph)")
2790
+ .optional(),
2791
+ include_imports: zod_1.z
2792
+ .boolean()
2793
+ .describe("Prepend file imports to results")
2794
+ .optional(),
2795
+ name_pattern: zod_1.z
2796
+ .string()
2797
+ .describe("Regex filter on symbol name")
2798
+ .optional(),
2799
+ scope: zod_1.z
2800
+ .string()
2801
+ .describe("'project' (default) or 'all' (search everything)")
2802
+ .optional(),
2803
+ projects: zod_1.z
2804
+ .string()
2805
+ .describe("Project names to include (comma-separated)")
2806
+ .optional(),
2807
+ exclude_projects: zod_1.z
2808
+ .string()
2809
+ .describe("Project names to exclude (comma-separated)")
2810
+ .optional(),
2811
+ seed_files: zod_1.z
2812
+ .string()
2813
+ .describe("Bias results toward your working context: comma-separated paths you have open (e.g. 'src/lib/llm/server.ts'). On-topic chunks in these files get lifted; off-topic ones are not.")
2814
+ .optional(),
2815
+ seed_symbols: zod_1.z
2816
+ .string()
2817
+ .describe("Bias results toward identifiers you're working with: comma-separated symbol names. Chunks defining a seeded symbol are preferred over mere callers.")
2818
+ .optional(),
2819
+ },
2820
+ }, handleSemanticSearch);
2821
+ tool("code_skeleton", {
2822
+ description: "File structure with bodies collapsed (~4x fewer tokens). Accepts file, directory, or comma-separated paths.",
2823
+ inputSchema: {
2824
+ target: zod_1.z
2825
+ .string()
2826
+ .describe("File, directory, or comma-separated paths"),
2827
+ limit: zod_1.z
2828
+ .number()
2829
+ .describe("Max files for directory mode (default 10)")
2830
+ .optional(),
2831
+ format: zod_1.z
2832
+ .string()
2833
+ .describe("'text' (default) or 'json'")
2834
+ .optional(),
2835
+ },
2836
+ }, handleCodeSkeleton);
2837
+ tool("trace_calls", {
2838
+ description: "Call graph: importers, callers (multi-hop), callees with file:line.",
2839
+ inputSchema: {
2840
+ symbol: zod_1.z.string().describe("Function/class name to trace"),
2841
+ depth: zod_1.z
2842
+ .number()
2843
+ .describe("Caller depth (default 1, max 3)")
2844
+ .optional(),
2845
+ },
2846
+ }, handleTraceCalls);
2847
+ tool("extract_symbol", {
2848
+ description: "Extract complete function/class body by symbol name.",
2849
+ inputSchema: {
2850
+ symbol: zod_1.z.string().describe("Symbol name to extract"),
2851
+ root: zod_1.z
2852
+ .string()
2853
+ .describe("Project root (absolute path)")
2854
+ .optional(),
2855
+ include_imports: zod_1.z
2856
+ .boolean()
2857
+ .describe("Prepend file imports")
2858
+ .optional(),
2859
+ },
2860
+ }, handleExtractSymbol);
2861
+ tool("peek_symbol", {
2862
+ description: "Compact symbol overview: signature + callers + callees.",
2863
+ inputSchema: {
2864
+ symbol: zod_1.z.string().describe("Symbol name"),
2865
+ root: zod_1.z
2866
+ .string()
2867
+ .describe("Project root (absolute path)")
2868
+ .optional(),
2869
+ depth: zod_1.z
2870
+ .number()
2871
+ .describe("Caller depth (default 1, max 3)")
2872
+ .optional(),
2873
+ },
2874
+ }, handlePeekSymbol);
2875
+ tool("dead", {
2876
+ description: "Report whether a symbol has zero inbound callers in the indexed call graph. Returns DEAD, PUBLIC EXPORT (exported with no internal callers), or LIVE with caller count. Hypothesis, not proof: dynamic dispatch, reflection, and string-built call sites won't show up.",
2877
+ inputSchema: {
2878
+ symbol: zod_1.z.string().describe("Symbol name to check"),
2879
+ root: zod_1.z
2880
+ .string()
2881
+ .describe("Project root (absolute path)")
2882
+ .optional(),
2883
+ },
2884
+ }, handleDead);
2885
+ tool("audit", {
2886
+ description: "Graph-summary of the indexed project in one call: god nodes (most depended-upon symbols), hub files (most depended-upon files), symbol-derived file dependency cycles, and dead-code candidates (non-exported symbols with zero inbound references). Built from the static reference graph; dynamic dispatch, reflection, and eval are invisible, so dead candidates are hypotheses; verify with the `dead` tool before acting.",
2887
+ inputSchema: {
2888
+ root: zod_1.z
2889
+ .string()
2890
+ .describe("Project root (default: current)")
2891
+ .optional(),
2892
+ top: zod_1.z
2893
+ .number()
2894
+ .describe("How many of each category to return (default 10)")
2895
+ .optional(),
2896
+ },
2897
+ }, handleAudit);
2898
+ tool("surprising_connections", {
2899
+ description: "Experimental orientation signal: embedding-similar cross-directory file pairs that are not directly connected by the static symbol graph. Useful for spotting duplicate or parallel logic. Requires experimental:true.",
2900
+ inputSchema: {
2901
+ experimental: zod_1.z
2902
+ .boolean()
2903
+ .describe("Must be true to acknowledge experimental signal quality"),
2904
+ root: zod_1.z
2905
+ .string()
2906
+ .describe("Project root (default: current)")
2907
+ .optional(),
2908
+ sample: zod_1.z
2909
+ .number()
2910
+ .describe("Indexed code chunks to sample (default 160, max 10000)")
2911
+ .optional(),
2912
+ neighbors: zod_1.z
2913
+ .number()
2914
+ .describe("Nearest neighbors per sampled chunk (default 20, max 200)")
2915
+ .optional(),
2916
+ top: zod_1.z
2917
+ .number()
2918
+ .describe("Grouped findings to return (default 10, max 100)")
2919
+ .optional(),
2920
+ dir_depth: zod_1.z
2921
+ .number()
2922
+ .describe("Directory bucket depth considered unsurprising")
2923
+ .optional(),
2924
+ min_similarity: zod_1.z
2925
+ .number()
2926
+ .describe("Minimum similarity 0-1 (default 0)")
2927
+ .optional(),
2928
+ max_rows: zod_1.z
2929
+ .number()
2930
+ .describe(`Maximum indexed rows to scan (default 50000, max ${surprising_connections_1.MAX_SURPRISE_ROWS})`)
2931
+ .optional(),
2932
+ in: zod_1.z
2933
+ .string()
2934
+ .describe("Restrict to a sub-path of the project")
2935
+ .optional(),
2936
+ exclude: zod_1.z
2937
+ .string()
2938
+ .describe("Exclude a sub-path of the project")
2939
+ .optional(),
2940
+ include_tests: zod_1.z
2941
+ .boolean()
2942
+ .describe("Include test files")
2943
+ .optional(),
2944
+ include_eval: zod_1.z
2945
+ .boolean()
2946
+ .describe("Include eval/experiment/script files")
2947
+ .optional(),
2948
+ },
2949
+ }, handleSurprisingConnections);
2950
+ tool("get_neighbors", {
2951
+ description: "Graph primitive: symbols reachable from a node along call edges within N hops. direction 'callees' = what it calls (outbound), 'callers' = what calls it (inbound). Each result carries its hop distance and definition location. Static call graph — same caveats as `dead`/`audit`.",
2952
+ inputSchema: {
2953
+ symbol: zod_1.z.string().describe("Starting symbol"),
2954
+ direction: zod_1.z
2955
+ .enum(["callers", "callees"])
2956
+ .describe("Edge direction (default callees)")
2957
+ .optional(),
2958
+ max_hops: zod_1.z
2959
+ .number()
2960
+ .describe("Max hops (default 2, max 5)")
2961
+ .optional(),
2962
+ root: zod_1.z
2963
+ .string()
2964
+ .describe("Project root (absolute path)")
2965
+ .optional(),
2966
+ },
2967
+ }, handleGetNeighbors);
2968
+ tool("find_paths", {
2969
+ description: "Graph primitive: shortest call-graph path between two symbols, as a symbol sequence. direction 'callees' searches outward from `from` (does `from` transitively call `to`?), 'callers' searches inward. Returns 'no path' if unreachable within max_hops.",
2970
+ inputSchema: {
2971
+ from: zod_1.z.string().describe("Start symbol"),
2972
+ to: zod_1.z.string().describe("Target symbol"),
2973
+ direction: zod_1.z
2974
+ .enum(["callers", "callees"])
2975
+ .describe("Search direction (default callees)")
2976
+ .optional(),
2977
+ max_hops: zod_1.z
2978
+ .number()
2979
+ .describe("Max hops (default 6, max 10)")
2980
+ .optional(),
2981
+ root: zod_1.z
2982
+ .string()
2983
+ .describe("Project root (absolute path)")
2984
+ .optional(),
2985
+ },
2986
+ }, handleFindPaths);
2987
+ tool("subgraph_for_files", {
2988
+ description: "Graph primitive: the local dependency subgraph for a set of files — symbols they define, the call edges among those symbols, and their outbound external dependencies. Paths may be absolute or project-root-relative.",
2989
+ inputSchema: {
2990
+ files: zod_1.z
2991
+ .array(zod_1.z.string())
2992
+ .describe("File paths (absolute or root-relative)"),
2993
+ root: zod_1.z
2994
+ .string()
2995
+ .describe("Project root (absolute path)")
2996
+ .optional(),
2997
+ },
2998
+ }, handleSubgraphForFiles);
2999
+ tool("list_symbols", {
3000
+ description: "List indexed symbols with role and export status.",
3001
+ inputSchema: {
3002
+ pattern: zod_1.z
3003
+ .string()
3004
+ .describe("Name filter (case-insensitive)")
3005
+ .optional(),
3006
+ limit: zod_1.z.number().describe("Max results (default 20)").optional(),
3007
+ path: zod_1.z.string().describe("Path prefix filter").optional(),
3008
+ },
3009
+ }, handleListSymbols);
3010
+ tool("index_status", {
3011
+ description: "Index health: chunks, files, projects, watcher status.",
3012
+ inputSchema: {},
3013
+ _meta: { "anthropic/alwaysLoad": true },
3014
+ }, handleIndexStatus);
3015
+ tool("list_projects", {
3016
+ description: "List every gmax-indexed project (name, root, status, chunk count). Use this to pick a search scope when you're not inside a specific repo: feed a name into semantic_search's projects: param, or use scope:'all'. The working-directory project is marked (current).",
3017
+ inputSchema: {},
3018
+ _meta: { "anthropic/alwaysLoad": true },
3019
+ }, handleListProjects);
3020
+ tool("summarize_directory", {
3021
+ description: "Generate LLM summaries for indexed chunks.",
3022
+ inputSchema: {
3023
+ path: zod_1.z
3024
+ .string()
3025
+ .describe("Directory to summarize (default: project root)")
3026
+ .optional(),
3027
+ limit: zod_1.z
3028
+ .number()
3029
+ .describe("Max chunks (default 200, max 5000)")
3030
+ .optional(),
3031
+ },
3032
+ }, handleSummarizeDirectory);
3033
+ tool("summarize_project", {
3034
+ description: "Project overview: languages, structure, roles, key symbols, entry points.",
3035
+ inputSchema: {
3036
+ root: zod_1.z
3037
+ .string()
3038
+ .describe("Project root (default: current)")
3039
+ .optional(),
3040
+ },
3041
+ }, handleSummarizeProject);
3042
+ tool("related_files", {
3043
+ description: "Find dependencies and dependents of a file by shared symbols.",
3044
+ inputSchema: {
3045
+ file: zod_1.z.string().describe("File path relative to project root"),
3046
+ limit: zod_1.z
3047
+ .number()
3048
+ .describe("Max results per direction (default 10)")
3049
+ .optional(),
3050
+ },
3051
+ }, handleRelatedFiles);
3052
+ tool("recent_changes", {
3053
+ description: "Recently modified indexed files with timestamps.",
3054
+ inputSchema: {
3055
+ limit: zod_1.z
3056
+ .number()
3057
+ .describe("Max files to return (default 20)")
3058
+ .optional(),
3059
+ root: zod_1.z
3060
+ .string()
3061
+ .describe("Project root (defaults to current project)")
3062
+ .optional(),
3063
+ },
3064
+ }, handleRecentChanges);
3065
+ tool("diff_changes", {
3066
+ description: "Search code scoped to git changes. Omit ref for uncommitted changes.",
3067
+ inputSchema: {
3068
+ ref: zod_1.z
3069
+ .string()
3070
+ .describe("Git ref to diff against (e.g. main, HEAD~5)")
3071
+ .optional(),
3072
+ query: zod_1.z
3073
+ .string()
3074
+ .describe("Semantic search within changed files")
3075
+ .optional(),
3076
+ limit: zod_1.z.number().describe("Max results (default 10)").optional(),
3077
+ role: zod_1.z
3078
+ .string()
3079
+ .describe("Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
3080
+ .optional(),
3081
+ },
3082
+ }, handleDiffChanges);
3083
+ tool("find_tests", {
3084
+ description: "Find tests that exercise a symbol or file via reverse call graph.",
3085
+ inputSchema: {
3086
+ target: zod_1.z.string().describe("Symbol name or file path"),
3087
+ depth: zod_1.z
3088
+ .number()
3089
+ .describe("Caller traversal depth 1-3 (default 1)")
3090
+ .optional(),
3091
+ },
3092
+ }, handleFindTests);
3093
+ tool("impact_analysis", {
3094
+ description: "Change impact: dependents and affected tests for a symbol or file.",
3095
+ inputSchema: {
3096
+ target: zod_1.z.string().describe("Symbol name or file path"),
3097
+ depth: zod_1.z
3098
+ .number()
3099
+ .describe("Caller traversal depth 1-3 (default 1)")
3100
+ .optional(),
3101
+ rollup: zod_1.z
3102
+ .boolean()
3103
+ .describe("Return export/package rollup TSV rows")
3104
+ .optional(),
3105
+ top: zod_1.z
3106
+ .number()
3107
+ .describe("Max rows per rollup section (default 10)")
3108
+ .optional(),
3109
+ },
3110
+ }, handleImpactAnalysis);
3111
+ tool("find_similar", {
3112
+ description: "Find semantically similar code using vector similarity.",
3113
+ inputSchema: {
3114
+ target: zod_1.z.string().describe("Symbol name or file path"),
3115
+ limit: zod_1.z.number().describe("Max results (default 5)").optional(),
3116
+ threshold: zod_1.z
3117
+ .number()
3118
+ .describe("Min similarity 0-1 (default 0)")
3119
+ .optional(),
3120
+ },
3121
+ }, handleFindSimilar);
3122
+ tool("build_context", {
3123
+ description: "Token-budgeted topic summary (search + skeleton + extract).",
3124
+ inputSchema: {
3125
+ topic: zod_1.z
3126
+ .string()
3127
+ .describe("Natural language topic or directory path"),
3128
+ budget: zod_1.z.number().describe("Max tokens (default 4000)").optional(),
3129
+ limit: zod_1.z
3130
+ .number()
3131
+ .describe("Search result limit (default 10)")
3132
+ .optional(),
3133
+ },
3134
+ }, handleBuildContext);
3135
+ tool("investigate", {
3136
+ description: "Agentic codebase Q&A: a local LLM answers questions using search, trace, peek, impact, and related tools. Requires LLM to be enabled (gmax llm on).",
3137
+ inputSchema: {
3138
+ question: zod_1.z
3139
+ .string()
3140
+ .describe("Natural language question about the codebase"),
3141
+ max_rounds: zod_1.z
3142
+ .number()
3143
+ .describe("Max tool-call rounds (default 10)")
3144
+ .optional(),
3145
+ },
3146
+ }, handleInvestigate);
3147
+ tool("review_commit", {
3148
+ description: "Review a git commit for bugs, breaking changes, and security issues using local LLM + codebase context. Returns structured findings. Requires LLM to be enabled (gmax llm on).",
3149
+ inputSchema: {
3150
+ commit: zod_1.z
3151
+ .string()
3152
+ .describe("Git ref to review (default: HEAD)")
3153
+ .optional(),
3154
+ },
3155
+ }, handleReviewCommit);
3156
+ tool("review_report", {
3157
+ description: "Get the accumulated code review report for the current project. Returns findings from all reviewed commits.",
3158
+ inputSchema: {
3159
+ json: zod_1.z
3160
+ .boolean()
3161
+ .describe("Return raw JSON instead of text (default: false)")
3162
+ .optional(),
3163
+ },
3164
+ }, handleReviewReport);
3165
+ tool("review_risk", {
3166
+ description: "Deterministic risk ranking of the symbols a commit's diff touches, ordered by blast radius (inbound callers) × test presence × file churn. No LLM required — fast, graph + git only. Use to triage what a change endangers before a deep review.",
3167
+ inputSchema: {
3168
+ commit: zod_1.z
3169
+ .string()
3170
+ .describe("Git ref to analyze (default: HEAD)")
3171
+ .optional(),
3172
+ },
3173
+ }, handleReviewRisk);
3174
+ return server;
2660
3175
  }
2661
- tool("semantic_search", {
2662
- description: "Search code by meaning. Use scope:'all' for cross-project. Prefer CLI: gmax \"query\" --plain",
2663
- inputSchema: {
2664
- query: zod_1.z
2665
- .string()
2666
- .describe("Natural language query (5+ words recommended)"),
2667
- limit: zod_1.z
2668
- .number()
2669
- .describe("Max results (default 3, max 50)")
2670
- .optional(),
2671
- root: zod_1.z
2672
- .string()
2673
- .describe("Registered project name or exact root")
2674
- .optional(),
2675
- path: zod_1.z
2676
- .string()
2677
- .describe("Path prefix filter (e.g. 'src/auth/')")
2678
- .optional(),
2679
- detail: zod_1.z
2680
- .string()
2681
- .describe("'pointer' (default), 'code', or 'full'")
2682
- .optional(),
2683
- min_score: zod_1.z
2684
- .number()
2685
- .describe("Min score 0-1 (default 0)")
2686
- .optional(),
2687
- max_per_file: zod_1.z.number().describe("Max results per file").optional(),
2688
- file: zod_1.z
2689
- .string()
2690
- .describe("Filename filter (e.g. 'syncer.ts')")
2691
- .optional(),
2692
- exclude: zod_1.z
2693
- .string()
2694
- .describe("Exclude path prefix (e.g. 'tests/')")
2695
- .optional(),
2696
- language: zod_1.z
2697
- .string()
2698
- .describe("Extension filter (e.g. 'ts', 'py')")
2699
- .optional(),
2700
- role: zod_1.z
2701
- .string()
2702
- .describe("'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'")
2703
- .optional(),
2704
- context_lines: zod_1.z
2705
- .number()
2706
- .describe("Lines before/after chunk (max 20)")
2707
- .optional(),
2708
- mode: zod_1.z
2709
- .string()
2710
- .describe("'default' or 'symbol' (appends call graph)")
2711
- .optional(),
2712
- include_imports: zod_1.z
2713
- .boolean()
2714
- .describe("Prepend file imports to results")
2715
- .optional(),
2716
- name_pattern: zod_1.z
2717
- .string()
2718
- .describe("Regex filter on symbol name")
2719
- .optional(),
2720
- scope: zod_1.z
2721
- .string()
2722
- .describe("'project' (default) or 'all' (search everything)")
2723
- .optional(),
2724
- projects: zod_1.z
2725
- .string()
2726
- .describe("Project names to include (comma-separated)")
2727
- .optional(),
2728
- exclude_projects: zod_1.z
2729
- .string()
2730
- .describe("Project names to exclude (comma-separated)")
2731
- .optional(),
2732
- seed_files: zod_1.z
2733
- .string()
2734
- .describe("Bias results toward your working context: comma-separated paths you have open (e.g. 'src/lib/llm/server.ts'). On-topic chunks in these files get lifted; off-topic ones are not.")
2735
- .optional(),
2736
- seed_symbols: zod_1.z
2737
- .string()
2738
- .describe("Bias results toward identifiers you're working with: comma-separated symbol names. Chunks defining a seeded symbol are preferred over mere callers.")
2739
- .optional(),
2740
- },
2741
- }, handleSemanticSearch);
2742
- tool("code_skeleton", {
2743
- description: "File structure with bodies collapsed (~4x fewer tokens). Accepts file, directory, or comma-separated paths.",
2744
- inputSchema: {
2745
- target: zod_1.z
2746
- .string()
2747
- .describe("File, directory, or comma-separated paths"),
2748
- limit: zod_1.z
2749
- .number()
2750
- .describe("Max files for directory mode (default 10)")
2751
- .optional(),
2752
- format: zod_1.z.string().describe("'text' (default) or 'json'").optional(),
2753
- },
2754
- }, handleCodeSkeleton);
2755
- tool("trace_calls", {
2756
- description: "Call graph: importers, callers (multi-hop), callees with file:line.",
2757
- inputSchema: {
2758
- symbol: zod_1.z.string().describe("Function/class name to trace"),
2759
- depth: zod_1.z
2760
- .number()
2761
- .describe("Caller depth (default 1, max 3)")
2762
- .optional(),
2763
- },
2764
- }, handleTraceCalls);
2765
- tool("extract_symbol", {
2766
- description: "Extract complete function/class body by symbol name.",
2767
- inputSchema: {
2768
- symbol: zod_1.z.string().describe("Symbol name to extract"),
2769
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2770
- include_imports: zod_1.z
2771
- .boolean()
2772
- .describe("Prepend file imports")
2773
- .optional(),
2774
- },
2775
- }, handleExtractSymbol);
2776
- tool("peek_symbol", {
2777
- description: "Compact symbol overview: signature + callers + callees.",
2778
- inputSchema: {
2779
- symbol: zod_1.z.string().describe("Symbol name"),
2780
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2781
- depth: zod_1.z
2782
- .number()
2783
- .describe("Caller depth (default 1, max 3)")
2784
- .optional(),
2785
- },
2786
- }, handlePeekSymbol);
2787
- tool("dead", {
2788
- description: "Report whether a symbol has zero inbound callers in the indexed call graph. Returns DEAD, PUBLIC EXPORT (exported with no internal callers), or LIVE with caller count. Hypothesis, not proof: dynamic dispatch, reflection, and string-built call sites won't show up.",
2789
- inputSchema: {
2790
- symbol: zod_1.z.string().describe("Symbol name to check"),
2791
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2792
- },
2793
- }, handleDead);
2794
- tool("audit", {
2795
- description: "Graph-summary of the indexed project in one call: god nodes (most depended-upon symbols), hub files (most depended-upon files), symbol-derived file dependency cycles, and dead-code candidates (non-exported symbols with zero inbound references). Built from the static reference graph; dynamic dispatch, reflection, and eval are invisible, so dead candidates are hypotheses; verify with the `dead` tool before acting.",
2796
- inputSchema: {
2797
- root: zod_1.z
2798
- .string()
2799
- .describe("Project root (default: current)")
2800
- .optional(),
2801
- top: zod_1.z
2802
- .number()
2803
- .describe("How many of each category to return (default 10)")
2804
- .optional(),
2805
- },
2806
- }, handleAudit);
2807
- tool("surprising_connections", {
2808
- description: "Experimental orientation signal: embedding-similar cross-directory file pairs that are not directly connected by the static symbol graph. Useful for spotting duplicate or parallel logic. Requires experimental:true.",
2809
- inputSchema: {
2810
- experimental: zod_1.z
2811
- .boolean()
2812
- .describe("Must be true to acknowledge experimental signal quality"),
2813
- root: zod_1.z
2814
- .string()
2815
- .describe("Project root (default: current)")
2816
- .optional(),
2817
- sample: zod_1.z
2818
- .number()
2819
- .describe("Indexed code chunks to sample (default 160, max 10000)")
2820
- .optional(),
2821
- neighbors: zod_1.z
2822
- .number()
2823
- .describe("Nearest neighbors per sampled chunk (default 20, max 200)")
2824
- .optional(),
2825
- top: zod_1.z
2826
- .number()
2827
- .describe("Grouped findings to return (default 10, max 100)")
2828
- .optional(),
2829
- dir_depth: zod_1.z
2830
- .number()
2831
- .describe("Directory bucket depth considered unsurprising")
2832
- .optional(),
2833
- min_similarity: zod_1.z
2834
- .number()
2835
- .describe("Minimum similarity 0-1 (default 0)")
2836
- .optional(),
2837
- max_rows: zod_1.z
2838
- .number()
2839
- .describe(`Maximum indexed rows to scan (default 50000, max ${surprising_connections_1.MAX_SURPRISE_ROWS})`)
2840
- .optional(),
2841
- in: zod_1.z
2842
- .string()
2843
- .describe("Restrict to a sub-path of the project")
2844
- .optional(),
2845
- exclude: zod_1.z
2846
- .string()
2847
- .describe("Exclude a sub-path of the project")
2848
- .optional(),
2849
- include_tests: zod_1.z.boolean().describe("Include test files").optional(),
2850
- include_eval: zod_1.z
2851
- .boolean()
2852
- .describe("Include eval/experiment/script files")
2853
- .optional(),
2854
- },
2855
- }, handleSurprisingConnections);
2856
- tool("get_neighbors", {
2857
- description: "Graph primitive: symbols reachable from a node along call edges within N hops. direction 'callees' = what it calls (outbound), 'callers' = what calls it (inbound). Each result carries its hop distance and definition location. Static call graph — same caveats as `dead`/`audit`.",
2858
- inputSchema: {
2859
- symbol: zod_1.z.string().describe("Starting symbol"),
2860
- direction: zod_1.z
2861
- .enum(["callers", "callees"])
2862
- .describe("Edge direction (default callees)")
2863
- .optional(),
2864
- max_hops: zod_1.z
2865
- .number()
2866
- .describe("Max hops (default 2, max 5)")
2867
- .optional(),
2868
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2869
- },
2870
- }, handleGetNeighbors);
2871
- tool("find_paths", {
2872
- description: "Graph primitive: shortest call-graph path between two symbols, as a symbol sequence. direction 'callees' searches outward from `from` (does `from` transitively call `to`?), 'callers' searches inward. Returns 'no path' if unreachable within max_hops.",
2873
- inputSchema: {
2874
- from: zod_1.z.string().describe("Start symbol"),
2875
- to: zod_1.z.string().describe("Target symbol"),
2876
- direction: zod_1.z
2877
- .enum(["callers", "callees"])
2878
- .describe("Search direction (default callees)")
2879
- .optional(),
2880
- max_hops: zod_1.z
2881
- .number()
2882
- .describe("Max hops (default 6, max 10)")
2883
- .optional(),
2884
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2885
- },
2886
- }, handleFindPaths);
2887
- tool("subgraph_for_files", {
2888
- description: "Graph primitive: the local dependency subgraph for a set of files — symbols they define, the call edges among those symbols, and their outbound external dependencies. Paths may be absolute or project-root-relative.",
2889
- inputSchema: {
2890
- files: zod_1.z
2891
- .array(zod_1.z.string())
2892
- .describe("File paths (absolute or root-relative)"),
2893
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2894
- },
2895
- }, handleSubgraphForFiles);
2896
- tool("list_symbols", {
2897
- description: "List indexed symbols with role and export status.",
2898
- inputSchema: {
2899
- pattern: zod_1.z
2900
- .string()
2901
- .describe("Name filter (case-insensitive)")
2902
- .optional(),
2903
- limit: zod_1.z.number().describe("Max results (default 20)").optional(),
2904
- path: zod_1.z.string().describe("Path prefix filter").optional(),
2905
- },
2906
- }, handleListSymbols);
2907
- tool("index_status", {
2908
- description: "Index health: chunks, files, projects, watcher status.",
2909
- inputSchema: {},
2910
- _meta: { "anthropic/alwaysLoad": true },
2911
- }, handleIndexStatus);
2912
- tool("list_projects", {
2913
- description: "List every gmax-indexed project (name, root, status, chunk count). Use this to pick a search scope when you're not inside a specific repo: feed a name into semantic_search's projects: param, or use scope:'all'. The working-directory project is marked (current).",
2914
- inputSchema: {},
2915
- _meta: { "anthropic/alwaysLoad": true },
2916
- }, handleListProjects);
2917
- tool("summarize_directory", {
2918
- description: "Generate LLM summaries for indexed chunks.",
2919
- inputSchema: {
2920
- path: zod_1.z
2921
- .string()
2922
- .describe("Directory to summarize (default: project root)")
2923
- .optional(),
2924
- limit: zod_1.z
2925
- .number()
2926
- .describe("Max chunks (default 200, max 5000)")
2927
- .optional(),
2928
- },
2929
- }, handleSummarizeDirectory);
2930
- tool("summarize_project", {
2931
- description: "Project overview: languages, structure, roles, key symbols, entry points.",
2932
- inputSchema: {
2933
- root: zod_1.z
2934
- .string()
2935
- .describe("Project root (default: current)")
2936
- .optional(),
2937
- },
2938
- }, handleSummarizeProject);
2939
- tool("related_files", {
2940
- description: "Find dependencies and dependents of a file by shared symbols.",
2941
- inputSchema: {
2942
- file: zod_1.z.string().describe("File path relative to project root"),
2943
- limit: zod_1.z
2944
- .number()
2945
- .describe("Max results per direction (default 10)")
2946
- .optional(),
2947
- },
2948
- }, handleRelatedFiles);
2949
- tool("recent_changes", {
2950
- description: "Recently modified indexed files with timestamps.",
2951
- inputSchema: {
2952
- limit: zod_1.z
2953
- .number()
2954
- .describe("Max files to return (default 20)")
2955
- .optional(),
2956
- root: zod_1.z
2957
- .string()
2958
- .describe("Project root (defaults to current project)")
2959
- .optional(),
2960
- },
2961
- }, handleRecentChanges);
2962
- tool("diff_changes", {
2963
- description: "Search code scoped to git changes. Omit ref for uncommitted changes.",
2964
- inputSchema: {
2965
- ref: zod_1.z
2966
- .string()
2967
- .describe("Git ref to diff against (e.g. main, HEAD~5)")
2968
- .optional(),
2969
- query: zod_1.z
2970
- .string()
2971
- .describe("Semantic search within changed files")
2972
- .optional(),
2973
- limit: zod_1.z.number().describe("Max results (default 10)").optional(),
2974
- role: zod_1.z
2975
- .string()
2976
- .describe("Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
2977
- .optional(),
2978
- },
2979
- }, handleDiffChanges);
2980
- tool("find_tests", {
2981
- description: "Find tests that exercise a symbol or file via reverse call graph.",
2982
- inputSchema: {
2983
- target: zod_1.z.string().describe("Symbol name or file path"),
2984
- depth: zod_1.z
2985
- .number()
2986
- .describe("Caller traversal depth 1-3 (default 1)")
2987
- .optional(),
2988
- },
2989
- }, handleFindTests);
2990
- tool("impact_analysis", {
2991
- description: "Change impact: dependents and affected tests for a symbol or file.",
2992
- inputSchema: {
2993
- target: zod_1.z.string().describe("Symbol name or file path"),
2994
- depth: zod_1.z
2995
- .number()
2996
- .describe("Caller traversal depth 1-3 (default 1)")
2997
- .optional(),
2998
- rollup: zod_1.z
2999
- .boolean()
3000
- .describe("Return export/package rollup TSV rows")
3001
- .optional(),
3002
- top: zod_1.z
3003
- .number()
3004
- .describe("Max rows per rollup section (default 10)")
3005
- .optional(),
3006
- },
3007
- }, handleImpactAnalysis);
3008
- tool("find_similar", {
3009
- description: "Find semantically similar code using vector similarity.",
3010
- inputSchema: {
3011
- target: zod_1.z.string().describe("Symbol name or file path"),
3012
- limit: zod_1.z.number().describe("Max results (default 5)").optional(),
3013
- threshold: zod_1.z
3014
- .number()
3015
- .describe("Min similarity 0-1 (default 0)")
3016
- .optional(),
3017
- },
3018
- }, handleFindSimilar);
3019
- tool("build_context", {
3020
- description: "Token-budgeted topic summary (search + skeleton + extract).",
3021
- inputSchema: {
3022
- topic: zod_1.z
3023
- .string()
3024
- .describe("Natural language topic or directory path"),
3025
- budget: zod_1.z.number().describe("Max tokens (default 4000)").optional(),
3026
- limit: zod_1.z
3027
- .number()
3028
- .describe("Search result limit (default 10)")
3029
- .optional(),
3030
- },
3031
- }, handleBuildContext);
3032
- tool("investigate", {
3033
- description: "Agentic codebase Q&A: a local LLM answers questions using search, trace, peek, impact, and related tools. Requires LLM to be enabled (gmax llm on).",
3034
- inputSchema: {
3035
- question: zod_1.z
3036
- .string()
3037
- .describe("Natural language question about the codebase"),
3038
- max_rounds: zod_1.z
3039
- .number()
3040
- .describe("Max tool-call rounds (default 10)")
3041
- .optional(),
3042
- },
3043
- }, handleInvestigate);
3044
- tool("review_commit", {
3045
- description: "Review a git commit for bugs, breaking changes, and security issues using local LLM + codebase context. Returns structured findings. Requires LLM to be enabled (gmax llm on).",
3046
- inputSchema: {
3047
- commit: zod_1.z
3048
- .string()
3049
- .describe("Git ref to review (default: HEAD)")
3050
- .optional(),
3051
- },
3052
- }, handleReviewCommit);
3053
- tool("review_report", {
3054
- description: "Get the accumulated code review report for the current project. Returns findings from all reviewed commits.",
3055
- inputSchema: {
3056
- json: zod_1.z
3057
- .boolean()
3058
- .describe("Return raw JSON instead of text (default: false)")
3059
- .optional(),
3060
- },
3061
- }, handleReviewReport);
3062
- tool("review_risk", {
3063
- description: "Deterministic risk ranking of the symbols a commit's diff touches, ordered by blast radius (inbound callers) × test presence × file churn. No LLM required — fast, graph + git only. Use to triage what a change endangers before a deep review.",
3064
- inputSchema: {
3065
- commit: zod_1.z
3066
- .string()
3067
- .describe("Git ref to analyze (default: HEAD)")
3068
- .optional(),
3069
- },
3070
- }, handleReviewRisk);
3071
- yield server.connect(transport);
3072
- // Start the singleton daemon in the background without delaying MCP startup.
3073
- void Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client"))).then(({ ensureDaemonRunning }) => ensureDaemonRunning())
3074
- .catch((e) => console.error("[MCP] Daemon startup failed:", e));
3075
- // Kick off index readiness check and watcher in background
3076
- ensureIndexReady().catch((e) => console.error("[MCP] Index readiness check failed:", e));
3077
- ensureWatcher();
3078
- // Keep this session's project leased while the session lives. The daemon
3079
- // only watches projects some session holds.
3080
- setInterval(() => void ensureWatcher(), MCP_WATCH_LEASE_RENEW_MS).unref();
3176
+ // Local-only, dual-era MCP: client-owned stdin/stdout pipes only.
3177
+ stdio = (0, stdio_1.serveStdio)(createServer, {
3178
+ legacy: "serve",
3179
+ onerror: (error) => console.error("[MCP] Transport error:", error),
3180
+ });
3081
3181
  }));