grepmax 0.26.45 → 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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.45",
13
+ "version": "0.26.46",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -128,6 +128,8 @@ Plugins auto-update when you run `npm install -g grepmax@latest` — no need to
128
128
 
129
129
  MCP is local-only: the client spawns it and communicates through stdin/stdout pipes. It has no HTTP, SSE, or TCP listener and cannot be reached over the network. The daemon socket is restricted to your user account.
130
130
 
131
+ `gmax mcp` uses the official MCP SDK v2.3.0 and serves both legacy (2025 handshake) and modern (2026-07-28 discovery and per-request metadata) clients over the same stdio entry. Discovery and catalog listing perform no daemon, model or watcher startup. Index-reading tools acquire watch leases lazily; one timer renews the primary projects actually used, and disconnect closes the SDK connection and its timer.
132
+
131
133
  `gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents). Search-backed tools use the singleton daemon when available, avoiding a separate embedding worker per MCP session, and use an in-process worker for supported offline/secondary-store access. An unsupported daemon command requires `gmax watch restart`.
132
134
 
133
135
  `semantic_search`, `trace_calls`, `dead`, and `index_status` also advertise output schemas and return `structuredContent` with `schemaVersion: 1`, alongside the existing text. Search matches follow the same scope and display filters and include paths, one-based start/end lines, symbols, scores (null when unavailable), and warnings. Trace results preserve caller ancestry and edge confidence; unresolved callees have no location, and omitted callee/importer counts are explicit. Graph conclusions carry `approximate: true`. Index health includes embedding identity, watcher/reconciliation state, and the last compaction result; unavailable health is null or unobserved. These four tools advertise read-only, non-destructive, closed-world annotations. Other tools retain their current contracts.
@@ -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");
@@ -374,12 +374,22 @@ exports.mcp = new commander_1.Command("mcp")
374
374
  // an MCP session holds no reader marker under ~/.gmax/lancedb.lease/readers/
375
375
  // while a daemon serving the verbs is up.
376
376
  let _skeletonizer = null;
377
- let _indexReady = false;
378
- // Nothing to close: the tools open no store of their own.
379
- 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
+ };
380
390
  process.on("SIGINT", exit);
381
391
  process.on("SIGTERM", exit);
382
- // MCP SDK doesn't handle stdin close — exit when the client disconnects
392
+ // Exit promptly on disconnect, including sessions with a lease timer.
383
393
  process.stdin.on("end", exit);
384
394
  process.stdin.on("close", exit);
385
395
  process.on("unhandledRejection", (reason, promise) => {
@@ -395,7 +405,7 @@ exports.mcp = new commander_1.Command("mcp")
395
405
  console.debug = (..._args) => { };
396
406
  // --- Project context ---
397
407
  const projectRoot = (0, project_root_1.findProjectRoot)(process.cwd());
398
- const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
408
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot, { dryRun: true });
399
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; };
400
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)(); };
401
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)(); };
@@ -598,18 +608,6 @@ exports.mcp = new commander_1.Command("mcp")
598
608
  return _skeletonizer;
599
609
  });
600
610
  }
601
- // --- Index sync ---
602
- function ensureIndexReady() {
603
- return __awaiter(this, void 0, void 0, function* () {
604
- if (_indexReady)
605
- return;
606
- const projects = storeProjects();
607
- const isRegistered = projects.some((p) => p.root === currentRoot());
608
- if (isRegistered) {
609
- _indexReady = true;
610
- }
611
- });
612
- }
613
611
  // --- Background watcher ---
614
612
  // This server's watch lease. It names our PID, so the daemon drops it the
615
613
  // minute this session exits; the TTL only covers a wedged server.
@@ -628,6 +626,17 @@ exports.mcp = new commander_1.Command("mcp")
628
626
  (0, stores_1.matchStore)(watchRoot).kind !== "primary")
629
627
  return;
630
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
+ }
631
640
  if (result.ok && !result.reused) {
632
641
  console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
633
642
  }
@@ -2629,519 +2638,544 @@ exports.mcp = new commander_1.Command("mcp")
2629
2638
  });
2630
2639
  }
2631
2640
  // --- MCP server setup ---
2632
- // Local-only MCP: client-owned pipes, no HTTP/SSE/TCP listener or host option.
2633
- const transport = new stdio_js_1.StdioServerTransport();
2634
- const server = new mcp_js_1.McpServer({
2635
- name: "gmax",
2636
- version: JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), {
2637
- encoding: "utf-8",
2638
- })).version,
2639
- }, {
2640
- capabilities: {
2641
- tools: {},
2642
- },
2643
- instructions: "gmax is a local semantic code-search index over one or more projects. " +
2644
- "By default tools operate on the project at the server's working directory. " +
2645
- "If you are NOT inside a specific repo, or want a different one: call " +
2646
- '`list_projects` to see what\'s indexed, then pass `projects:"name"` (or ' +
2647
- '`scope:"all"`) to `semantic_search`. When the working directory isn\'t an ' +
2648
- "indexed project, `semantic_search` errors instead of searching everything " +
2649
- "implicitly. Prefer `semantic_search` (detail:'pointer') for discovery, then " +
2650
- "`extract_symbol`/`peek_symbol`/`code_skeleton` to read, and " +
2651
- "`trace_calls`/`impact_analysis`/`find_tests` for call-graph questions. " +
2652
- "Use `audit` and experimental `surprising_connections` for project orientation.",
2653
- });
2654
- // Best-effort query logging, applied uniformly to every tool exactly as the
2655
- // old single CallToolRequestSchema dispatch did before each return.
2656
- const logToolCall = (name, toolArgs, startMs, result) => __awaiter(void 0, void 0, void 0, function* () {
2657
- var _a, _b;
2658
- var _c;
2659
- try {
2660
- const { logQuery } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/query-log")));
2661
- 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 : "";
2662
- const resultLines = text.split("\n").filter((l) => l.trim()).length;
2663
- logQuery({
2664
- ts: new Date().toISOString(),
2665
- source: "mcp",
2666
- tool: name,
2667
- query: mcpLogQuery(name, toolArgs),
2668
- project: currentRoot(),
2669
- results: resultLines,
2670
- ms: Date.now() - startMs,
2671
- error: result.isError ? text.slice(0, 200) : undefined,
2672
- });
2673
- }
2674
- catch (_d) { }
2675
- });
2676
- // Register a tool, wrapping its handler with timing + query logging. Zod raw
2677
- // shapes give us free input validation (the SDK rejects calls that violate
2678
- // the schema before the handler runs); handlers stay defensively coded.
2679
- function tool(name, config, handler) {
2680
- server.registerTool(name, Object.assign(Object.assign(Object.assign({}, config), (mcp_results_1.MCP_READ_OUTPUT_SCHEMAS[name]
2681
- ? {
2682
- outputSchema: mcp_results_1.MCP_READ_OUTPUT_SCHEMAS[name],
2683
- annotations: {
2684
- readOnlyHint: true,
2685
- destructiveHint: false,
2686
- openWorldHint: false,
2687
- },
2688
- }
2689
- : {})), { inputSchema: Object.assign({ root: zod_1.z.string().optional() }, config.inputSchema) }), (rawArgs) => __awaiter(this, void 0, void 0, function* () {
2690
- let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
2691
- const startMs = Date.now();
2692
- 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;
2693
2669
  try {
2694
- if (typeof args.root === "string" && args.root.trim()) {
2695
- const raw = args.root.trim();
2696
- if (path.isAbsolute(raw) || raw.includes(path.sep))
2697
- target = path.resolve(raw);
2698
- else {
2699
- const projects = [
2700
- ...(0, project_registry_1.listProjects)(),
2701
- ...(0, store_context_1.storeInventory)().flatMap((store) => store.projects),
2702
- ].filter((project, i, all) => all.findIndex((p) => p.root === project.root) === i);
2703
- const selected = resolveMcpProject(raw, projectRoot, projects);
2704
- if (!selected)
2705
- return err(`Unknown or ambiguous registered project: ${raw}. Use list_projects or an exact root.`);
2706
- target = selected.root;
2707
- }
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
+ },
2708
2698
  }
2709
- return yield (0, store_context_1.withStoreContext)(target, () => __awaiter(this, void 0, void 0, function* () {
2710
- if (args.root !== undefined) {
2711
- const selected = resolveMcpProject(args.root, currentRoot(), storeProjects());
2712
- if (!selected)
2713
- return err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
2714
- args = Object.assign(Object.assign({}, args), { root: selected.root });
2715
- (0, store_context_1.currentStoreContext)().root = selected.root;
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
+ }
2716
2718
  }
2717
- const result = yield handler(args);
2718
- yield logToolCall(name, args, startMs, result);
2719
- return result;
2720
- }));
2721
- }
2722
- catch (error) {
2723
- return toolError("Tool failed", error);
2724
- }
2725
- }));
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
+ }));
2731
+ }
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;
2726
3175
  }
2727
- tool("semantic_search", {
2728
- description: "Search code by meaning. Use scope:'all' for cross-project. Prefer CLI: gmax \"query\" --plain",
2729
- inputSchema: {
2730
- query: zod_1.z
2731
- .string()
2732
- .describe("Natural language query (5+ words recommended)"),
2733
- limit: zod_1.z
2734
- .number()
2735
- .describe("Max results (default 3, max 50)")
2736
- .optional(),
2737
- root: zod_1.z
2738
- .string()
2739
- .describe("Registered project name or exact root")
2740
- .optional(),
2741
- path: zod_1.z
2742
- .string()
2743
- .describe("Path prefix filter (e.g. 'src/auth/')")
2744
- .optional(),
2745
- detail: zod_1.z
2746
- .string()
2747
- .describe("'pointer' (default), 'code', or 'full'")
2748
- .optional(),
2749
- min_score: zod_1.z
2750
- .number()
2751
- .describe("Min score 0-1 (default 0)")
2752
- .optional(),
2753
- max_per_file: zod_1.z.number().describe("Max results per file").optional(),
2754
- file: zod_1.z
2755
- .string()
2756
- .describe("Filename filter (e.g. 'syncer.ts')")
2757
- .optional(),
2758
- exclude: zod_1.z
2759
- .string()
2760
- .describe("Exclude path prefix (e.g. 'tests/')")
2761
- .optional(),
2762
- language: zod_1.z
2763
- .string()
2764
- .describe("Extension filter (e.g. 'ts', 'py')")
2765
- .optional(),
2766
- role: zod_1.z
2767
- .string()
2768
- .describe("'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'")
2769
- .optional(),
2770
- context_lines: zod_1.z
2771
- .number()
2772
- .describe("Lines before/after chunk (max 20)")
2773
- .optional(),
2774
- mode: zod_1.z
2775
- .string()
2776
- .describe("'default' or 'symbol' (appends call graph)")
2777
- .optional(),
2778
- include_imports: zod_1.z
2779
- .boolean()
2780
- .describe("Prepend file imports to results")
2781
- .optional(),
2782
- name_pattern: zod_1.z
2783
- .string()
2784
- .describe("Regex filter on symbol name")
2785
- .optional(),
2786
- scope: zod_1.z
2787
- .string()
2788
- .describe("'project' (default) or 'all' (search everything)")
2789
- .optional(),
2790
- projects: zod_1.z
2791
- .string()
2792
- .describe("Project names to include (comma-separated)")
2793
- .optional(),
2794
- exclude_projects: zod_1.z
2795
- .string()
2796
- .describe("Project names to exclude (comma-separated)")
2797
- .optional(),
2798
- seed_files: zod_1.z
2799
- .string()
2800
- .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.")
2801
- .optional(),
2802
- seed_symbols: zod_1.z
2803
- .string()
2804
- .describe("Bias results toward identifiers you're working with: comma-separated symbol names. Chunks defining a seeded symbol are preferred over mere callers.")
2805
- .optional(),
2806
- },
2807
- }, handleSemanticSearch);
2808
- tool("code_skeleton", {
2809
- description: "File structure with bodies collapsed (~4x fewer tokens). Accepts file, directory, or comma-separated paths.",
2810
- inputSchema: {
2811
- target: zod_1.z
2812
- .string()
2813
- .describe("File, directory, or comma-separated paths"),
2814
- limit: zod_1.z
2815
- .number()
2816
- .describe("Max files for directory mode (default 10)")
2817
- .optional(),
2818
- format: zod_1.z.string().describe("'text' (default) or 'json'").optional(),
2819
- },
2820
- }, handleCodeSkeleton);
2821
- tool("trace_calls", {
2822
- description: "Call graph: importers, callers (multi-hop), callees with file:line.",
2823
- inputSchema: {
2824
- symbol: zod_1.z.string().describe("Function/class name to trace"),
2825
- depth: zod_1.z
2826
- .number()
2827
- .describe("Caller depth (default 1, max 3)")
2828
- .optional(),
2829
- },
2830
- }, handleTraceCalls);
2831
- tool("extract_symbol", {
2832
- description: "Extract complete function/class body by symbol name.",
2833
- inputSchema: {
2834
- symbol: zod_1.z.string().describe("Symbol name to extract"),
2835
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2836
- include_imports: zod_1.z
2837
- .boolean()
2838
- .describe("Prepend file imports")
2839
- .optional(),
2840
- },
2841
- }, handleExtractSymbol);
2842
- tool("peek_symbol", {
2843
- description: "Compact symbol overview: signature + callers + callees.",
2844
- inputSchema: {
2845
- symbol: zod_1.z.string().describe("Symbol name"),
2846
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2847
- depth: zod_1.z
2848
- .number()
2849
- .describe("Caller depth (default 1, max 3)")
2850
- .optional(),
2851
- },
2852
- }, handlePeekSymbol);
2853
- tool("dead", {
2854
- 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.",
2855
- inputSchema: {
2856
- symbol: zod_1.z.string().describe("Symbol name to check"),
2857
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2858
- },
2859
- }, handleDead);
2860
- tool("audit", {
2861
- 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.",
2862
- inputSchema: {
2863
- root: zod_1.z
2864
- .string()
2865
- .describe("Project root (default: current)")
2866
- .optional(),
2867
- top: zod_1.z
2868
- .number()
2869
- .describe("How many of each category to return (default 10)")
2870
- .optional(),
2871
- },
2872
- }, handleAudit);
2873
- tool("surprising_connections", {
2874
- 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.",
2875
- inputSchema: {
2876
- experimental: zod_1.z
2877
- .boolean()
2878
- .describe("Must be true to acknowledge experimental signal quality"),
2879
- root: zod_1.z
2880
- .string()
2881
- .describe("Project root (default: current)")
2882
- .optional(),
2883
- sample: zod_1.z
2884
- .number()
2885
- .describe("Indexed code chunks to sample (default 160, max 10000)")
2886
- .optional(),
2887
- neighbors: zod_1.z
2888
- .number()
2889
- .describe("Nearest neighbors per sampled chunk (default 20, max 200)")
2890
- .optional(),
2891
- top: zod_1.z
2892
- .number()
2893
- .describe("Grouped findings to return (default 10, max 100)")
2894
- .optional(),
2895
- dir_depth: zod_1.z
2896
- .number()
2897
- .describe("Directory bucket depth considered unsurprising")
2898
- .optional(),
2899
- min_similarity: zod_1.z
2900
- .number()
2901
- .describe("Minimum similarity 0-1 (default 0)")
2902
- .optional(),
2903
- max_rows: zod_1.z
2904
- .number()
2905
- .describe(`Maximum indexed rows to scan (default 50000, max ${surprising_connections_1.MAX_SURPRISE_ROWS})`)
2906
- .optional(),
2907
- in: zod_1.z
2908
- .string()
2909
- .describe("Restrict to a sub-path of the project")
2910
- .optional(),
2911
- exclude: zod_1.z
2912
- .string()
2913
- .describe("Exclude a sub-path of the project")
2914
- .optional(),
2915
- include_tests: zod_1.z.boolean().describe("Include test files").optional(),
2916
- include_eval: zod_1.z
2917
- .boolean()
2918
- .describe("Include eval/experiment/script files")
2919
- .optional(),
2920
- },
2921
- }, handleSurprisingConnections);
2922
- tool("get_neighbors", {
2923
- 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`.",
2924
- inputSchema: {
2925
- symbol: zod_1.z.string().describe("Starting symbol"),
2926
- direction: zod_1.z
2927
- .enum(["callers", "callees"])
2928
- .describe("Edge direction (default callees)")
2929
- .optional(),
2930
- max_hops: zod_1.z
2931
- .number()
2932
- .describe("Max hops (default 2, max 5)")
2933
- .optional(),
2934
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2935
- },
2936
- }, handleGetNeighbors);
2937
- tool("find_paths", {
2938
- 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.",
2939
- inputSchema: {
2940
- from: zod_1.z.string().describe("Start symbol"),
2941
- to: zod_1.z.string().describe("Target symbol"),
2942
- direction: zod_1.z
2943
- .enum(["callers", "callees"])
2944
- .describe("Search direction (default callees)")
2945
- .optional(),
2946
- max_hops: zod_1.z
2947
- .number()
2948
- .describe("Max hops (default 6, max 10)")
2949
- .optional(),
2950
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2951
- },
2952
- }, handleFindPaths);
2953
- tool("subgraph_for_files", {
2954
- 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.",
2955
- inputSchema: {
2956
- files: zod_1.z
2957
- .array(zod_1.z.string())
2958
- .describe("File paths (absolute or root-relative)"),
2959
- root: zod_1.z.string().describe("Project root (absolute path)").optional(),
2960
- },
2961
- }, handleSubgraphForFiles);
2962
- tool("list_symbols", {
2963
- description: "List indexed symbols with role and export status.",
2964
- inputSchema: {
2965
- pattern: zod_1.z
2966
- .string()
2967
- .describe("Name filter (case-insensitive)")
2968
- .optional(),
2969
- limit: zod_1.z.number().describe("Max results (default 20)").optional(),
2970
- path: zod_1.z.string().describe("Path prefix filter").optional(),
2971
- },
2972
- }, handleListSymbols);
2973
- tool("index_status", {
2974
- description: "Index health: chunks, files, projects, watcher status.",
2975
- inputSchema: {},
2976
- _meta: { "anthropic/alwaysLoad": true },
2977
- }, handleIndexStatus);
2978
- tool("list_projects", {
2979
- 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).",
2980
- inputSchema: {},
2981
- _meta: { "anthropic/alwaysLoad": true },
2982
- }, handleListProjects);
2983
- tool("summarize_directory", {
2984
- description: "Generate LLM summaries for indexed chunks.",
2985
- inputSchema: {
2986
- path: zod_1.z
2987
- .string()
2988
- .describe("Directory to summarize (default: project root)")
2989
- .optional(),
2990
- limit: zod_1.z
2991
- .number()
2992
- .describe("Max chunks (default 200, max 5000)")
2993
- .optional(),
2994
- },
2995
- }, handleSummarizeDirectory);
2996
- tool("summarize_project", {
2997
- description: "Project overview: languages, structure, roles, key symbols, entry points.",
2998
- inputSchema: {
2999
- root: zod_1.z
3000
- .string()
3001
- .describe("Project root (default: current)")
3002
- .optional(),
3003
- },
3004
- }, handleSummarizeProject);
3005
- tool("related_files", {
3006
- description: "Find dependencies and dependents of a file by shared symbols.",
3007
- inputSchema: {
3008
- file: zod_1.z.string().describe("File path relative to project root"),
3009
- limit: zod_1.z
3010
- .number()
3011
- .describe("Max results per direction (default 10)")
3012
- .optional(),
3013
- },
3014
- }, handleRelatedFiles);
3015
- tool("recent_changes", {
3016
- description: "Recently modified indexed files with timestamps.",
3017
- inputSchema: {
3018
- limit: zod_1.z
3019
- .number()
3020
- .describe("Max files to return (default 20)")
3021
- .optional(),
3022
- root: zod_1.z
3023
- .string()
3024
- .describe("Project root (defaults to current project)")
3025
- .optional(),
3026
- },
3027
- }, handleRecentChanges);
3028
- tool("diff_changes", {
3029
- description: "Search code scoped to git changes. Omit ref for uncommitted changes.",
3030
- inputSchema: {
3031
- ref: zod_1.z
3032
- .string()
3033
- .describe("Git ref to diff against (e.g. main, HEAD~5)")
3034
- .optional(),
3035
- query: zod_1.z
3036
- .string()
3037
- .describe("Semantic search within changed files")
3038
- .optional(),
3039
- limit: zod_1.z.number().describe("Max results (default 10)").optional(),
3040
- role: zod_1.z
3041
- .string()
3042
- .describe("Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
3043
- .optional(),
3044
- },
3045
- }, handleDiffChanges);
3046
- tool("find_tests", {
3047
- description: "Find tests that exercise a symbol or file via reverse call graph.",
3048
- inputSchema: {
3049
- target: zod_1.z.string().describe("Symbol name or file path"),
3050
- depth: zod_1.z
3051
- .number()
3052
- .describe("Caller traversal depth 1-3 (default 1)")
3053
- .optional(),
3054
- },
3055
- }, handleFindTests);
3056
- tool("impact_analysis", {
3057
- description: "Change impact: dependents and affected tests for a symbol or file.",
3058
- inputSchema: {
3059
- target: zod_1.z.string().describe("Symbol name or file path"),
3060
- depth: zod_1.z
3061
- .number()
3062
- .describe("Caller traversal depth 1-3 (default 1)")
3063
- .optional(),
3064
- rollup: zod_1.z
3065
- .boolean()
3066
- .describe("Return export/package rollup TSV rows")
3067
- .optional(),
3068
- top: zod_1.z
3069
- .number()
3070
- .describe("Max rows per rollup section (default 10)")
3071
- .optional(),
3072
- },
3073
- }, handleImpactAnalysis);
3074
- tool("find_similar", {
3075
- description: "Find semantically similar code using vector similarity.",
3076
- inputSchema: {
3077
- target: zod_1.z.string().describe("Symbol name or file path"),
3078
- limit: zod_1.z.number().describe("Max results (default 5)").optional(),
3079
- threshold: zod_1.z
3080
- .number()
3081
- .describe("Min similarity 0-1 (default 0)")
3082
- .optional(),
3083
- },
3084
- }, handleFindSimilar);
3085
- tool("build_context", {
3086
- description: "Token-budgeted topic summary (search + skeleton + extract).",
3087
- inputSchema: {
3088
- topic: zod_1.z
3089
- .string()
3090
- .describe("Natural language topic or directory path"),
3091
- budget: zod_1.z.number().describe("Max tokens (default 4000)").optional(),
3092
- limit: zod_1.z
3093
- .number()
3094
- .describe("Search result limit (default 10)")
3095
- .optional(),
3096
- },
3097
- }, handleBuildContext);
3098
- tool("investigate", {
3099
- 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).",
3100
- inputSchema: {
3101
- question: zod_1.z
3102
- .string()
3103
- .describe("Natural language question about the codebase"),
3104
- max_rounds: zod_1.z
3105
- .number()
3106
- .describe("Max tool-call rounds (default 10)")
3107
- .optional(),
3108
- },
3109
- }, handleInvestigate);
3110
- tool("review_commit", {
3111
- 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).",
3112
- inputSchema: {
3113
- commit: zod_1.z
3114
- .string()
3115
- .describe("Git ref to review (default: HEAD)")
3116
- .optional(),
3117
- },
3118
- }, handleReviewCommit);
3119
- tool("review_report", {
3120
- description: "Get the accumulated code review report for the current project. Returns findings from all reviewed commits.",
3121
- inputSchema: {
3122
- json: zod_1.z
3123
- .boolean()
3124
- .describe("Return raw JSON instead of text (default: false)")
3125
- .optional(),
3126
- },
3127
- }, handleReviewReport);
3128
- tool("review_risk", {
3129
- 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.",
3130
- inputSchema: {
3131
- commit: zod_1.z
3132
- .string()
3133
- .describe("Git ref to analyze (default: HEAD)")
3134
- .optional(),
3135
- },
3136
- }, handleReviewRisk);
3137
- yield server.connect(transport);
3138
- // Start the singleton daemon in the background without delaying MCP startup.
3139
- void Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client"))).then(({ ensureDaemonRunning }) => ensureDaemonRunning())
3140
- .catch((e) => console.error("[MCP] Daemon startup failed:", e));
3141
- // Kick off index readiness check and watcher in background
3142
- ensureIndexReady().catch((e) => console.error("[MCP] Index readiness check failed:", e));
3143
- ensureWatcher();
3144
- // Keep this session's project leased while the session lives. The daemon
3145
- // only watches projects some session holds.
3146
- 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
+ });
3147
3181
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.45",
3
+ "version": "0.26.46",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -80,7 +80,8 @@
80
80
  "dependencies": {
81
81
  "@clack/prompts": "^1.8.1",
82
82
  "@huggingface/transformers": "^4.3.0",
83
- "@modelcontextprotocol/sdk": "^1.30.0",
83
+ "@modelcontextprotocol/server": "2.3.0",
84
+ "@opentelemetry/api": "^1.9.0",
84
85
  "@parcel/watcher": "^2.6.0",
85
86
  "apache-arrow": "^18.1.0",
86
87
  "cli-highlight": "^2.1.11",
@@ -93,30 +94,31 @@
93
94
  "openai": "^7.15.0",
94
95
  "ora": "^9.4.1",
95
96
  "proper-lockfile": "^4.1.2",
97
+ "reflect-metadata": "^0.2.2",
96
98
  "uuid": "^14.0.2",
97
99
  "web-tree-sitter": "^0.27.0",
98
- "zod": "^4.6.5",
99
- "@opentelemetry/api": "^1.9.0",
100
- "reflect-metadata": "^0.2.2"
100
+ "zod": "^4.6.5"
101
101
  },
102
102
  "devDependencies": {
103
103
  "@biomejs/biome": "2.5.13",
104
+ "@lancedb/lancedb": "0.38.0",
105
+ "@modelcontextprotocol/client": "2.3.0",
104
106
  "@types/node": "^26.6.1",
105
107
  "@types/proper-lockfile": "^4.1.4",
108
+ "mcp-sdk-v1-fixture": "npm:@modelcontextprotocol/sdk@1.30.0",
106
109
  "node-gyp": "^13.0.2",
107
110
  "tsx": "^4.23.13",
108
111
  "typescript": "^7.0.2",
109
112
  "vite": "^8.3.0",
110
- "vitest": "^5.0.1",
111
- "@lancedb/lancedb": "0.38.0"
113
+ "vitest": "^5.0.1"
112
114
  },
113
115
  "optionalDependencies": {
114
116
  "@lancedb/lancedb-darwin-arm64": "0.38.0",
115
- "@lancedb/lancedb-linux-x64-gnu": "0.38.0",
116
117
  "@lancedb/lancedb-linux-arm64-gnu": "0.38.0",
117
- "@lancedb/lancedb-linux-x64-musl": "0.38.0",
118
118
  "@lancedb/lancedb-linux-arm64-musl": "0.38.0",
119
- "@lancedb/lancedb-win32-x64-msvc": "0.38.0",
120
- "@lancedb/lancedb-win32-arm64-msvc": "0.38.0"
119
+ "@lancedb/lancedb-linux-x64-gnu": "0.38.0",
120
+ "@lancedb/lancedb-linux-x64-musl": "0.38.0",
121
+ "@lancedb/lancedb-win32-arm64-msvc": "0.38.0",
122
+ "@lancedb/lancedb-win32-x64-msvc": "0.38.0"
121
123
  }
122
124
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.45",
3
+ "version": "0.26.46",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",