@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 +10 -1
- package/dist/cli.d.ts +1 -15
- package/dist/cli.js +86 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +292 -3
- package/dist/main.d.ts +17 -0
- package/dist/policy.d.ts +1 -1
- package/package.json +4 -4
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
|
|
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:
|
|
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/
|
|
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
|
-
|
|
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((
|
|
443
|
-
process.once("SIGINT",
|
|
444
|
-
process.once("SIGTERM",
|
|
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 =
|
|
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(
|
|
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:
|
|
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.
|
|
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.
|
|
41
|
-
"@wrongstack/mcp": "1.1.
|
|
42
|
-
"@wrongstack/tools": "1.1.
|
|
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",
|