@wrongstack/codebase-index-mcp 1.1.1 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/adapter.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '@wrongstack/core/agent';
2
2
  import type { Tool } from '@wrongstack/core/types';
3
3
  import { MCPServer, type MCPServerToolHost } from '@wrongstack/mcp';
4
- import { type CodebaseIndexMcpPolicyOptions } from './policy.js';
4
+ import { type CodebaseIndexMcpPolicyOptions, type CodebaseIndexMcpToolName } from './policy.js';
5
5
  interface GraphBaseArgs {
6
6
  projectRoot: string;
7
7
  indexDir?: string | undefined;
@@ -22,6 +22,15 @@ export interface CodebaseIndexMcpToolHostOptions extends CodebaseIndexMcpPolicyO
22
22
  indexDir?: string | undefined;
23
23
  dependencies?: CodebaseIndexMcpDependencies | undefined;
24
24
  }
25
+ /**
26
+ * The navigation tools a coding agent reaches for most. Claude Code defers MCP
27
+ * tools behind a tool search, and an agent that must search before it can call
28
+ * the index falls back to its always-loaded grep; loading these three upfront
29
+ * costs little context and makes the index the shorter path.
30
+ */
31
+ export declare const ALWAYS_LOADED_TOOLS: ReadonlySet<CodebaseIndexMcpToolName>;
32
+ /** `InitializeResult.instructions`: shown to the client even while tools are deferred. */
33
+ export declare const CODEBASE_INDEX_MCP_INSTRUCTIONS: string;
25
34
  export declare function createCodebaseIndexMcpToolHost(projectRoot: string, opts?: CodebaseIndexMcpToolHostOptions): MCPServerToolHost;
26
35
  export declare function createCodebaseIndexMcpServer(projectRoot: string, opts?: CodebaseIndexMcpToolHostOptions): MCPServer;
27
36
  export {};
package/dist/cli.d.ts CHANGED
@@ -1,17 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export interface ParsedArgs {
3
- projectRoot: string;
4
- indexDir?: string | undefined;
5
- transport: 'stdio' | 'http';
6
- httpPort: number;
7
- httpHost: string;
8
- httpToken?: string | undefined;
9
- writable: boolean;
10
- help: boolean;
11
- }
12
- export declare function printHelp(stdout: NodeJS.WriteStream): void;
13
- export declare function parseArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): ParsedArgs;
14
- /** Resolve the checkout whose source files and local index are served. */
15
- export declare function resolveIndexProjectRoot(projectRoot: string): string;
16
- export declare function main(argv?: string[]): Promise<number>;
2
+ export * from './main.js';
17
3
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import { realpathSync as realpathSync2 } from "node:fs";
5
+ import * as path2 from "node:path";
6
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7
+
8
+ // src/main.ts
4
9
  import { realpathSync } from "node:fs";
5
10
  import * as path from "node:path";
6
- import { fileURLToPath as fileURLToPath2 } from "node:url";
7
11
  import { serveHttp, serveStdio } from "@wrongstack/mcp";
8
12
  import {
9
13
  checkCodebaseIndexServerHealth,
10
14
  ensureCodebaseIndexServer,
11
15
  resolveProjectIndexDaemonAvailability,
16
+ runStartupIndex,
12
17
  shutdownCodebaseIndexHost
13
18
  } from "@wrongstack/tools/codebase-index";
14
19
 
@@ -18,8 +23,14 @@ import {
18
23
  } from "@wrongstack/mcp";
19
24
  import {
20
25
  codebaseContextTool,
26
+ codebaseImpactAnalysisTool,
27
+ codebaseIncomingCallsTool,
21
28
  codebaseIndexTool,
29
+ codebaseOutgoingCallsTool,
30
+ codebaseReadSymbolTool,
31
+ codebaseRepoMapTool,
22
32
  codebaseSearchTool,
33
+ codebaseSkeletonTool,
23
34
  codebaseStatsTool,
24
35
  fileGraphService,
25
36
  packageGraphService,
@@ -33,7 +44,13 @@ var CODEBASE_INDEX_READ_TOOLS = [
33
44
  "codebase_stats",
34
45
  "codebase_package_graph",
35
46
  "codebase_file_graph",
36
- "codebase_symbol_graph"
47
+ "codebase_symbol_graph",
48
+ "codebase_incoming_calls",
49
+ "codebase_outgoing_calls",
50
+ "codebase_read_symbol",
51
+ "codebase_skeleton",
52
+ "codebase_impact_analysis",
53
+ "codebase_repo_map"
37
54
  ];
38
55
  var CODEBASE_INDEX_WRITE_TOOLS = ["codebase_index"];
39
56
  function selectCodebaseIndexMcpTools(opts = {}) {
@@ -99,12 +116,24 @@ var TOOL_DESCRIPTIONS = {
99
116
  codebase_package_graph: "Return the project package dependency graph from the authoritative Codebase Index service.",
100
117
  codebase_file_graph: "Return the file dependency graph for one package or package-path fragment.",
101
118
  codebase_symbol_graph: "Return the symbol dependency graph for one project-relative source file.",
119
+ codebase_incoming_calls: "List who calls or references a function, method or type: compact call sites with file and line. Use this, not codebase_symbol_graph, to find callers.",
120
+ codebase_outgoing_calls: "List what a function or method calls: compact call sites with file and line.",
121
+ codebase_read_symbol: "Return one symbol's source (function, class, method) by file and name, without reading the whole file.",
122
+ codebase_skeleton: "Outline a file or directory: exported signatures and declarations without bodies.",
123
+ codebase_impact_analysis: "Estimate what a change to a symbol affects: dependent symbols and files, optionally transitive.",
124
+ codebase_repo_map: "A ranked overview of the repository: the most central files and their key symbols.",
102
125
  codebase_index: "Build or incrementally refresh the project Codebase Index. Hidden unless the server starts with --writable."
103
126
  };
104
127
  var BUILTIN_TOOLS = {
105
128
  codebase_context: codebaseContextTool,
106
129
  codebase_search: codebaseSearchTool,
107
130
  codebase_stats: codebaseStatsTool,
131
+ codebase_incoming_calls: codebaseIncomingCallsTool,
132
+ codebase_outgoing_calls: codebaseOutgoingCallsTool,
133
+ codebase_read_symbol: codebaseReadSymbolTool,
134
+ codebase_skeleton: codebaseSkeletonTool,
135
+ codebase_impact_analysis: codebaseImpactAnalysisTool,
136
+ codebase_repo_map: codebaseRepoMapTool,
108
137
  codebase_index: codebaseIndexTool
109
138
  };
110
139
  function cloneToolSchema(name, tool) {
@@ -115,6 +144,12 @@ function cloneToolSchema(name, tool) {
115
144
  }
116
145
  return schema;
117
146
  }
147
+ var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
148
+ "codebase_search",
149
+ "codebase_incoming_calls",
150
+ "codebase_read_symbol"
151
+ ]);
152
+ var CODEBASE_INDEX_MCP_INSTRUCTIONS = "A prebuilt, ranked symbol index of this repository. Use it before grep/glob or whole-file reads to navigate code: codebase_search finds where a symbol is defined, codebase_incoming_calls / codebase_outgoing_calls list callers and callees, codebase_read_symbol returns one symbol's source, codebase_skeleton outlines a file or folder, codebase_impact_analysis estimates what a change affects, and codebase_context finds files relevant to a task. Grep stays the right tool for literal text such as strings, comments and config values.";
118
153
  function toolDescriptor(name) {
119
154
  const builtin = BUILTIN_TOOLS[name];
120
155
  const graphSchema = GRAPH_SCHEMAS[name];
@@ -122,7 +157,8 @@ function toolDescriptor(name) {
122
157
  return {
123
158
  name,
124
159
  description: TOOL_DESCRIPTIONS[name],
125
- inputSchema: builtin ? cloneToolSchema(name, builtin) : graphSchema
160
+ inputSchema: builtin ? cloneToolSchema(name, builtin) : graphSchema,
161
+ ...ALWAYS_LOADED_TOOLS.has(name) ? { _meta: { "anthropic/alwaysLoad": true } } : {}
126
162
  };
127
163
  }
128
164
  function createContext(projectRoot, indexDir) {
@@ -152,6 +188,15 @@ function validateArgs(name, args) {
152
188
  if (name === "codebase_file_graph" && !nonEmptyString(args["package"])) {
153
189
  return 'codebase_file_graph requires a non-empty string "package"';
154
190
  }
191
+ const builtin = BUILTIN_TOOLS[name];
192
+ if (builtin && name !== "codebase_search") {
193
+ const schema = builtin.inputSchema;
194
+ for (const field of schema.required ?? []) {
195
+ if (schema.properties?.[field]?.type === "string" && !nonEmptyString(args[field])) {
196
+ return `${name} requires a non-empty string "${field}"`;
197
+ }
198
+ }
199
+ }
155
200
  if (name === "codebase_symbol_graph" && !nonEmptyString(args["file"])) {
156
201
  return 'codebase_symbol_graph requires a non-empty string "file"';
157
202
  }
@@ -201,7 +246,12 @@ function createCodebaseIndexMcpToolHost(projectRoot, opts = {}) {
201
246
  }
202
247
  const content2 = await executeTool(builtin, args, context, signal);
203
248
  signal.throwIfAborted();
204
- return { content: content2, isError: isDegradedFailure(content2) };
249
+ if (isDegradedFailure(content2)) return { content: content2, isError: true };
250
+ const serialize = builtin.serialize;
251
+ return {
252
+ content: typeof serialize === "function" ? serialize(content2, args) : content2,
253
+ isError: false
254
+ };
205
255
  }
206
256
  const base = {
207
257
  projectRoot,
@@ -239,6 +289,7 @@ function createCodebaseIndexMcpServer(projectRoot, opts = {}) {
239
289
  return new MCPServer({
240
290
  host: createCodebaseIndexMcpToolHost(projectRoot, opts),
241
291
  serverInfo: { name: "wrongstack-codebase-index-mcp", version: SERVER_INFO.version },
292
+ instructions: CODEBASE_INDEX_MCP_INSTRUCTIONS,
242
293
  prompts: [
243
294
  {
244
295
  name: "explore-codebase",
@@ -251,7 +302,7 @@ function createCodebaseIndexMcpServer(projectRoot, opts = {}) {
251
302
  });
252
303
  }
253
304
 
254
- // src/cli.ts
305
+ // src/main.ts
255
306
  function printHelp(stdout) {
256
307
  stdout.write(
257
308
  [
@@ -271,6 +322,8 @@ function printHelp(stdout) {
271
322
  " Prefer WRONGSTACK_MCP_TOKEN \u2014 a command line is readable",
272
323
  " by other local processes (WS-064).",
273
324
  " --writable Also expose codebase_index for incremental/full rebuilds.",
325
+ " --watch Keep the index fresh while connected: catch up incrementally",
326
+ " at start, then reindex files changed on disk.",
274
327
  " -h, --help Show this message.",
275
328
  "",
276
329
  "The MCP process is an IPC client. The detached project daemon remains the only SQLite owner."
@@ -285,6 +338,7 @@ function parseArgs(argv, env = process.env) {
285
338
  httpPort: 0,
286
339
  httpHost: "127.0.0.1",
287
340
  writable: false,
341
+ watch: false,
288
342
  help: false
289
343
  };
290
344
  const warn = (event, message) => {
@@ -342,6 +396,9 @@ function parseArgs(argv, env = process.env) {
342
396
  case "--writable":
343
397
  parsed.writable = true;
344
398
  break;
399
+ case "--watch":
400
+ parsed.watch = true;
401
+ break;
345
402
  case "-h":
346
403
  case "--help":
347
404
  parsed.help = true;
@@ -405,7 +462,9 @@ async function main(argv = process.argv.slice(2)) {
405
462
  await ensureCodebaseIndexServer({
406
463
  projectRoot,
407
464
  ...args.indexDir ? { indexDir: args.indexDir } : {},
408
- watchExternal: false
465
+ // A client that edits outside WrongStack (an external agent) makes the
466
+ // index stale unless the project server watches the disk for it.
467
+ watchExternal: args.watch
409
468
  });
410
469
  const health = await checkCodebaseIndexServerHealth(projectRoot, args.indexDir);
411
470
  if (health.status === "unresponsive") {
@@ -421,11 +480,24 @@ async function main(argv = process.argv.slice(2)) {
421
480
  } finally {
422
481
  clearInterval(startupLease);
423
482
  }
483
+ if (args.watch) {
484
+ void runStartupIndex({ projectRoot, ...args.indexDir ? { indexDir: args.indexDir } : {} }).then(
485
+ (result) => process.stderr.write(
486
+ `${SERVER_INFO.name}: index caught up (${JSON.stringify(result).slice(0, 160)})
487
+ `
488
+ )
489
+ ).catch(
490
+ (error) => process.stderr.write(
491
+ `${SERVER_INFO.name}: index catch-up failed: ${error instanceof Error ? error.message : String(error)}
492
+ `
493
+ )
494
+ );
495
+ }
424
496
  const server = createCodebaseIndexMcpServer(projectRoot, {
425
497
  writable: args.writable,
426
498
  ...args.indexDir ? { indexDir: args.indexDir } : {}
427
499
  });
428
- const policyText = `writable=${String(args.writable)}`;
500
+ const policyText = `writable=${String(args.writable)} watch=${String(args.watch)}`;
429
501
  if (args.transport === "http") {
430
502
  try {
431
503
  const handle = await serveHttp(server, {
@@ -439,9 +511,9 @@ async function main(argv = process.argv.slice(2)) {
439
511
  `${SERVER_INFO.name}: ready at ${handle.url} \u2014 projectRoot=${projectRoot} transport=http ${policyText}${args.httpToken ? " [token auth]" : ""}
440
512
  `
441
513
  );
442
- await new Promise((resolve3) => {
443
- process.once("SIGINT", resolve3);
444
- process.once("SIGTERM", resolve3);
514
+ await new Promise((resolve4) => {
515
+ process.once("SIGINT", resolve4);
516
+ process.once("SIGTERM", resolve4);
445
517
  });
446
518
  await handle.close();
447
519
  return 0;
@@ -461,17 +533,19 @@ async function main(argv = process.argv.slice(2)) {
461
533
  await shutdownCodebaseIndexHost();
462
534
  }
463
535
  }
536
+
537
+ // src/cli.ts
464
538
  function isMainModule() {
465
539
  const entry = process.argv[1];
466
540
  if (!entry) return false;
467
541
  const self = fileURLToPath2(import.meta.url);
468
542
  const comparablePath = (value) => {
469
- const normalized = path.resolve(value).replace(/^\\\\\?\\/, "");
543
+ const normalized = path2.resolve(value).replace(/^\\\\\?\\/, "");
470
544
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
471
545
  };
472
546
  if (comparablePath(entry) === comparablePath(self)) return true;
473
547
  try {
474
- return comparablePath(realpathSync(entry)) === comparablePath(realpathSync(self));
548
+ return comparablePath(realpathSync2(entry)) === comparablePath(realpathSync2(self));
475
549
  } catch {
476
550
  return false;
477
551
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { type CodebaseIndexMcpDependencies, type CodebaseIndexMcpToolHostOptions, createCodebaseIndexMcpServer, createCodebaseIndexMcpToolHost, } from './adapter.js';
2
+ export { main as runCodebaseIndexMcpServer } from './main.js';
2
3
  export { CODEBASE_INDEX_READ_TOOLS, CODEBASE_INDEX_WRITE_TOOLS, type CodebaseIndexMcpPolicyOptions, type CodebaseIndexMcpToolName, selectCodebaseIndexMcpTools, } from './policy.js';
3
4
  export { SERVER_INFO } from './version.js';
4
5
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -4,8 +4,14 @@ import {
4
4
  } from "@wrongstack/mcp";
5
5
  import {
6
6
  codebaseContextTool,
7
+ codebaseImpactAnalysisTool,
8
+ codebaseIncomingCallsTool,
7
9
  codebaseIndexTool,
10
+ codebaseOutgoingCallsTool,
11
+ codebaseReadSymbolTool,
12
+ codebaseRepoMapTool,
8
13
  codebaseSearchTool,
14
+ codebaseSkeletonTool,
9
15
  codebaseStatsTool,
10
16
  fileGraphService,
11
17
  packageGraphService,
@@ -19,7 +25,13 @@ var CODEBASE_INDEX_READ_TOOLS = [
19
25
  "codebase_stats",
20
26
  "codebase_package_graph",
21
27
  "codebase_file_graph",
22
- "codebase_symbol_graph"
28
+ "codebase_symbol_graph",
29
+ "codebase_incoming_calls",
30
+ "codebase_outgoing_calls",
31
+ "codebase_read_symbol",
32
+ "codebase_skeleton",
33
+ "codebase_impact_analysis",
34
+ "codebase_repo_map"
23
35
  ];
24
36
  var CODEBASE_INDEX_WRITE_TOOLS = ["codebase_index"];
25
37
  function selectCodebaseIndexMcpTools(opts = {}) {
@@ -85,12 +97,24 @@ var TOOL_DESCRIPTIONS = {
85
97
  codebase_package_graph: "Return the project package dependency graph from the authoritative Codebase Index service.",
86
98
  codebase_file_graph: "Return the file dependency graph for one package or package-path fragment.",
87
99
  codebase_symbol_graph: "Return the symbol dependency graph for one project-relative source file.",
100
+ codebase_incoming_calls: "List who calls or references a function, method or type: compact call sites with file and line. Use this, not codebase_symbol_graph, to find callers.",
101
+ codebase_outgoing_calls: "List what a function or method calls: compact call sites with file and line.",
102
+ codebase_read_symbol: "Return one symbol's source (function, class, method) by file and name, without reading the whole file.",
103
+ codebase_skeleton: "Outline a file or directory: exported signatures and declarations without bodies.",
104
+ codebase_impact_analysis: "Estimate what a change to a symbol affects: dependent symbols and files, optionally transitive.",
105
+ codebase_repo_map: "A ranked overview of the repository: the most central files and their key symbols.",
88
106
  codebase_index: "Build or incrementally refresh the project Codebase Index. Hidden unless the server starts with --writable."
89
107
  };
90
108
  var BUILTIN_TOOLS = {
91
109
  codebase_context: codebaseContextTool,
92
110
  codebase_search: codebaseSearchTool,
93
111
  codebase_stats: codebaseStatsTool,
112
+ codebase_incoming_calls: codebaseIncomingCallsTool,
113
+ codebase_outgoing_calls: codebaseOutgoingCallsTool,
114
+ codebase_read_symbol: codebaseReadSymbolTool,
115
+ codebase_skeleton: codebaseSkeletonTool,
116
+ codebase_impact_analysis: codebaseImpactAnalysisTool,
117
+ codebase_repo_map: codebaseRepoMapTool,
94
118
  codebase_index: codebaseIndexTool
95
119
  };
96
120
  function cloneToolSchema(name, tool) {
@@ -101,6 +125,12 @@ function cloneToolSchema(name, tool) {
101
125
  }
102
126
  return schema;
103
127
  }
128
+ var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
129
+ "codebase_search",
130
+ "codebase_incoming_calls",
131
+ "codebase_read_symbol"
132
+ ]);
133
+ var CODEBASE_INDEX_MCP_INSTRUCTIONS = "A prebuilt, ranked symbol index of this repository. Use it before grep/glob or whole-file reads to navigate code: codebase_search finds where a symbol is defined, codebase_incoming_calls / codebase_outgoing_calls list callers and callees, codebase_read_symbol returns one symbol's source, codebase_skeleton outlines a file or folder, codebase_impact_analysis estimates what a change affects, and codebase_context finds files relevant to a task. Grep stays the right tool for literal text such as strings, comments and config values.";
104
134
  function toolDescriptor(name) {
105
135
  const builtin = BUILTIN_TOOLS[name];
106
136
  const graphSchema = GRAPH_SCHEMAS[name];
@@ -108,7 +138,8 @@ function toolDescriptor(name) {
108
138
  return {
109
139
  name,
110
140
  description: TOOL_DESCRIPTIONS[name],
111
- inputSchema: builtin ? cloneToolSchema(name, builtin) : graphSchema
141
+ inputSchema: builtin ? cloneToolSchema(name, builtin) : graphSchema,
142
+ ...ALWAYS_LOADED_TOOLS.has(name) ? { _meta: { "anthropic/alwaysLoad": true } } : {}
112
143
  };
113
144
  }
114
145
  function createContext(projectRoot, indexDir) {
@@ -138,6 +169,15 @@ function validateArgs(name, args) {
138
169
  if (name === "codebase_file_graph" && !nonEmptyString(args["package"])) {
139
170
  return 'codebase_file_graph requires a non-empty string "package"';
140
171
  }
172
+ const builtin = BUILTIN_TOOLS[name];
173
+ if (builtin && name !== "codebase_search") {
174
+ const schema = builtin.inputSchema;
175
+ for (const field of schema.required ?? []) {
176
+ if (schema.properties?.[field]?.type === "string" && !nonEmptyString(args[field])) {
177
+ return `${name} requires a non-empty string "${field}"`;
178
+ }
179
+ }
180
+ }
141
181
  if (name === "codebase_symbol_graph" && !nonEmptyString(args["file"])) {
142
182
  return 'codebase_symbol_graph requires a non-empty string "file"';
143
183
  }
@@ -187,7 +227,12 @@ function createCodebaseIndexMcpToolHost(projectRoot, opts = {}) {
187
227
  }
188
228
  const content2 = await executeTool(builtin, args, context, signal);
189
229
  signal.throwIfAborted();
190
- return { content: content2, isError: isDegradedFailure(content2) };
230
+ if (isDegradedFailure(content2)) return { content: content2, isError: true };
231
+ const serialize = builtin.serialize;
232
+ return {
233
+ content: typeof serialize === "function" ? serialize(content2, args) : content2,
234
+ isError: false
235
+ };
191
236
  }
192
237
  const base = {
193
238
  projectRoot,
@@ -225,6 +270,7 @@ function createCodebaseIndexMcpServer(projectRoot, opts = {}) {
225
270
  return new MCPServer({
226
271
  host: createCodebaseIndexMcpToolHost(projectRoot, opts),
227
272
  serverInfo: { name: "wrongstack-codebase-index-mcp", version: SERVER_INFO.version },
273
+ instructions: CODEBASE_INDEX_MCP_INSTRUCTIONS,
228
274
  prompts: [
229
275
  {
230
276
  name: "explore-codebase",
@@ -236,12 +282,255 @@ function createCodebaseIndexMcpServer(projectRoot, opts = {}) {
236
282
  ]
237
283
  });
238
284
  }
285
+
286
+ // src/main.ts
287
+ import { realpathSync } from "node:fs";
288
+ import * as path from "node:path";
289
+ import { serveHttp, serveStdio } from "@wrongstack/mcp";
290
+ import {
291
+ checkCodebaseIndexServerHealth,
292
+ ensureCodebaseIndexServer,
293
+ resolveProjectIndexDaemonAvailability,
294
+ runStartupIndex,
295
+ shutdownCodebaseIndexHost
296
+ } from "@wrongstack/tools/codebase-index";
297
+ function printHelp(stdout) {
298
+ stdout.write(
299
+ [
300
+ `${SERVER_INFO.name} v${SERVER_INFO.version} \u2014 WrongStack Codebase Index MCP server`,
301
+ "",
302
+ "Usage:",
303
+ ` ${SERVER_INFO.name} --project-root <path> [options]`,
304
+ "",
305
+ "Options:",
306
+ " --project-root <path> Project whose IPC-backed index should be served (required).",
307
+ " --index-dir <path> Override the project Codebase Index directory.",
308
+ " --stdio Use stdio transport (default).",
309
+ " --http Use HTTP transport.",
310
+ " --port <n> HTTP port (default 0 = ephemeral).",
311
+ " --host <h> HTTP bind host (default 127.0.0.1).",
312
+ " --token <t> Bearer token. Required for a non-loopback HTTP bind.",
313
+ " Prefer WRONGSTACK_MCP_TOKEN \u2014 a command line is readable",
314
+ " by other local processes (WS-064).",
315
+ " --writable Also expose codebase_index for incremental/full rebuilds.",
316
+ " --watch Keep the index fresh while connected: catch up incrementally",
317
+ " at start, then reindex files changed on disk.",
318
+ " -h, --help Show this message.",
319
+ "",
320
+ "The MCP process is an IPC client. The detached project daemon remains the only SQLite owner."
321
+ ].join("\n") + "\n"
322
+ );
323
+ }
324
+ var HTTP_TOKEN_ENV = "WRONGSTACK_MCP_TOKEN";
325
+ function parseArgs(argv, env = process.env) {
326
+ const parsed = {
327
+ projectRoot: "",
328
+ transport: "stdio",
329
+ httpPort: 0,
330
+ httpHost: "127.0.0.1",
331
+ writable: false,
332
+ watch: false,
333
+ help: false
334
+ };
335
+ const warn = (event, message) => {
336
+ console.warn(
337
+ JSON.stringify({ level: "warn", event, message, timestamp: (/* @__PURE__ */ new Date()).toISOString() })
338
+ );
339
+ };
340
+ for (let index = 0; index < argv.length; index++) {
341
+ const arg = argv[index];
342
+ const value = () => {
343
+ const next = argv[index + 1];
344
+ if (next === void 0 || next.startsWith("-")) {
345
+ warn("mcp_cli_flag_missing_value", `${arg} expects a value; ignoring it.`);
346
+ return void 0;
347
+ }
348
+ index++;
349
+ return next;
350
+ };
351
+ switch (arg) {
352
+ case "--project-root": {
353
+ const v = value();
354
+ if (v !== void 0) parsed.projectRoot = path.resolve(v);
355
+ break;
356
+ }
357
+ case "--index-dir": {
358
+ const v = value();
359
+ if (v !== void 0) parsed.indexDir = path.resolve(v);
360
+ break;
361
+ }
362
+ case "--stdio":
363
+ parsed.transport = "stdio";
364
+ break;
365
+ case "--http":
366
+ parsed.transport = "http";
367
+ break;
368
+ case "--port": {
369
+ const v = value();
370
+ if (v === void 0) break;
371
+ const port = Number(v);
372
+ if (Number.isInteger(port) && port >= 0 && port <= 65535) parsed.httpPort = port;
373
+ else
374
+ warn("mcp_cli_invalid_port", `--port ${v} is not a valid port; using an ephemeral port.`);
375
+ break;
376
+ }
377
+ case "--host": {
378
+ const v = value();
379
+ if (v !== void 0) parsed.httpHost = v;
380
+ break;
381
+ }
382
+ case "--token": {
383
+ const v = value();
384
+ if (v !== void 0) parsed.httpToken = v;
385
+ break;
386
+ }
387
+ case "--writable":
388
+ parsed.writable = true;
389
+ break;
390
+ case "--watch":
391
+ parsed.watch = true;
392
+ break;
393
+ case "-h":
394
+ case "--help":
395
+ parsed.help = true;
396
+ break;
397
+ default:
398
+ if (arg?.startsWith("-")) warn("mcp_cli_unknown_option", `unknown option ${arg} ignored.`);
399
+ break;
400
+ }
401
+ }
402
+ if (parsed.httpToken === void 0) {
403
+ const fromEnv = env[HTTP_TOKEN_ENV]?.trim();
404
+ if (fromEnv) parsed.httpToken = fromEnv;
405
+ } else if (parsed.httpToken.length > 0) {
406
+ console.warn(
407
+ `[mcp] --token puts the auth token in this process's command line, which other local processes can read. Prefer ${HTTP_TOKEN_ENV}.`
408
+ );
409
+ }
410
+ return parsed;
411
+ }
412
+ function resolveIndexProjectRoot(projectRoot) {
413
+ const resolved = path.resolve(projectRoot);
414
+ try {
415
+ return realpathSync(resolved);
416
+ } catch {
417
+ return resolved;
418
+ }
419
+ }
420
+ function availabilityError(availability) {
421
+ if (availability.kind === "available") return null;
422
+ if (availability.kind === "inline-requested") {
423
+ return "the Codebase Index daemon is disabled by WRONGSTACK_INDEX_INLINE or WRONGSTACK_INDEX_SERVER=0";
424
+ }
425
+ if (availability.kind === "missing-build") {
426
+ return "the @wrongstack/tools Codebase Index project-server build is missing";
427
+ }
428
+ return `the IPC endpoint is too long (${availability.byteLength}/${availability.maxBytes} bytes): ` + availability.endpoint;
429
+ }
430
+ async function main(argv = process.argv.slice(2)) {
431
+ const args = parseArgs(argv);
432
+ if (args.help) {
433
+ printHelp(process.stdout);
434
+ return 0;
435
+ }
436
+ if (!args.projectRoot) {
437
+ process.stderr.write(`${SERVER_INFO.name}: --project-root is required
438
+ `);
439
+ printHelp(process.stderr);
440
+ return 2;
441
+ }
442
+ const projectRoot = resolveIndexProjectRoot(args.projectRoot);
443
+ const availability = resolveProjectIndexDaemonAvailability(projectRoot, args.indexDir);
444
+ const unavailable = availabilityError(availability);
445
+ if (unavailable) {
446
+ process.stderr.write(`${SERVER_INFO.name}: cannot use project-scoped IPC: ${unavailable}
447
+ `);
448
+ return 3;
449
+ }
450
+ const startupLease = setInterval(() => {
451
+ }, 1e3);
452
+ try {
453
+ await ensureCodebaseIndexServer({
454
+ projectRoot,
455
+ ...args.indexDir ? { indexDir: args.indexDir } : {},
456
+ // A client that edits outside WrongStack (an external agent) makes the
457
+ // index stale unless the project server watches the disk for it.
458
+ watchExternal: args.watch
459
+ });
460
+ const health = await checkCodebaseIndexServerHealth(projectRoot, args.indexDir);
461
+ if (health.status === "unresponsive") {
462
+ throw new Error("project server health check was unresponsive");
463
+ }
464
+ } catch (error) {
465
+ await shutdownCodebaseIndexHost();
466
+ process.stderr.write(
467
+ `${SERVER_INFO.name}: cannot attach to Codebase Index project server for ${projectRoot}: ${error instanceof Error ? error.message : String(error)}
468
+ `
469
+ );
470
+ return 3;
471
+ } finally {
472
+ clearInterval(startupLease);
473
+ }
474
+ if (args.watch) {
475
+ void runStartupIndex({ projectRoot, ...args.indexDir ? { indexDir: args.indexDir } : {} }).then(
476
+ (result) => process.stderr.write(
477
+ `${SERVER_INFO.name}: index caught up (${JSON.stringify(result).slice(0, 160)})
478
+ `
479
+ )
480
+ ).catch(
481
+ (error) => process.stderr.write(
482
+ `${SERVER_INFO.name}: index catch-up failed: ${error instanceof Error ? error.message : String(error)}
483
+ `
484
+ )
485
+ );
486
+ }
487
+ const server = createCodebaseIndexMcpServer(projectRoot, {
488
+ writable: args.writable,
489
+ ...args.indexDir ? { indexDir: args.indexDir } : {}
490
+ });
491
+ const policyText = `writable=${String(args.writable)} watch=${String(args.watch)}`;
492
+ if (args.transport === "http") {
493
+ try {
494
+ const handle = await serveHttp(server, {
495
+ port: args.httpPort,
496
+ host: args.httpHost,
497
+ ...args.httpToken ? { token: args.httpToken } : {},
498
+ logger: { warn: (message) => process.stderr.write(`[codebase-index-mcp] ${message}
499
+ `) }
500
+ });
501
+ process.stderr.write(
502
+ `${SERVER_INFO.name}: ready at ${handle.url} \u2014 projectRoot=${projectRoot} transport=http ${policyText}${args.httpToken ? " [token auth]" : ""}
503
+ `
504
+ );
505
+ await new Promise((resolve3) => {
506
+ process.once("SIGINT", resolve3);
507
+ process.once("SIGTERM", resolve3);
508
+ });
509
+ await handle.close();
510
+ return 0;
511
+ } finally {
512
+ await shutdownCodebaseIndexHost();
513
+ }
514
+ }
515
+ try {
516
+ const handle = serveStdio(server);
517
+ process.stderr.write(
518
+ `${SERVER_INFO.name}: ready on stdio \u2014 projectRoot=${projectRoot} transport=stdio ${policyText}
519
+ `
520
+ );
521
+ await handle.done;
522
+ return 0;
523
+ } finally {
524
+ await shutdownCodebaseIndexHost();
525
+ }
526
+ }
239
527
  export {
240
528
  CODEBASE_INDEX_READ_TOOLS,
241
529
  CODEBASE_INDEX_WRITE_TOOLS,
242
530
  SERVER_INFO,
243
531
  createCodebaseIndexMcpServer,
244
532
  createCodebaseIndexMcpToolHost,
533
+ main as runCodebaseIndexMcpServer,
245
534
  selectCodebaseIndexMcpTools
246
535
  };
247
536
  //# sourceMappingURL=index.js.map
package/dist/main.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface ParsedArgs {
2
+ projectRoot: string;
3
+ indexDir?: string | undefined;
4
+ transport: 'stdio' | 'http';
5
+ httpPort: number;
6
+ httpHost: string;
7
+ httpToken?: string | undefined;
8
+ writable: boolean;
9
+ watch: boolean;
10
+ help: boolean;
11
+ }
12
+ export declare function printHelp(stdout: NodeJS.WriteStream): void;
13
+ export declare function parseArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): ParsedArgs;
14
+ /** Resolve the checkout whose source files and local index are served. */
15
+ export declare function resolveIndexProjectRoot(projectRoot: string): string;
16
+ export declare function main(argv?: string[]): Promise<number>;
17
+ //# sourceMappingURL=main.d.ts.map
package/dist/policy.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CODEBASE_INDEX_READ_TOOLS: readonly ['codebase_context', 'codebase_search', 'codebase_stats', 'codebase_package_graph', 'codebase_file_graph', 'codebase_symbol_graph'];
1
+ export declare const CODEBASE_INDEX_READ_TOOLS: readonly ['codebase_context', 'codebase_search', 'codebase_stats', 'codebase_package_graph', 'codebase_file_graph', 'codebase_symbol_graph', 'codebase_incoming_calls', 'codebase_outgoing_calls', 'codebase_read_symbol', 'codebase_skeleton', 'codebase_impact_analysis', 'codebase_repo_map'];
2
2
  export declare const CODEBASE_INDEX_WRITE_TOOLS: readonly ['codebase_index'];
3
3
  export type CodebaseIndexMcpReadToolName = (typeof CODEBASE_INDEX_READ_TOOLS)[number];
4
4
  export type CodebaseIndexMcpWriteToolName = (typeof CODEBASE_INDEX_WRITE_TOOLS)[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/codebase-index-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.1.4",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack Codebase Index as an MCP server, backed by the existing project-scoped IPC daemon.",
6
6
  "repository": {
@@ -37,9 +37,9 @@
37
37
  "clean": "bun -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
38
38
  },
39
39
  "dependencies": {
40
- "@wrongstack/core": "1.1.0",
41
- "@wrongstack/mcp": "1.1.0",
42
- "@wrongstack/tools": "1.1.0"
40
+ "@wrongstack/core": "1.1.2",
41
+ "@wrongstack/mcp": "1.1.2",
42
+ "@wrongstack/tools": "1.1.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^26.5.1",