codesearch-mcp 0.2.3-alpha → 0.3.0-alpha

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +46 -26
  3. package/dist/app.js +5 -19
  4. package/dist/src/chunking/AstChunker.d.ts +12 -0
  5. package/dist/src/chunking/AstChunker.js +30 -0
  6. package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
  7. package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
  8. package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
  9. package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
  10. package/dist/src/chunking/FragmentMerger.d.ts +11 -0
  11. package/dist/src/chunking/FragmentMerger.js +61 -0
  12. package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
  13. package/dist/src/chunking/ImportOnlyFilter.js +14 -0
  14. package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
  15. package/dist/src/chunking/LangchainTextSplitter.js +133 -0
  16. package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
  17. package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
  18. package/dist/src/chunking/RunPacker.d.ts +12 -0
  19. package/dist/src/chunking/RunPacker.js +84 -0
  20. package/dist/src/chunking/create-chunker.d.ts +3 -0
  21. package/dist/src/chunking/create-chunker.js +15 -0
  22. package/dist/src/chunking/interfaces.d.ts +27 -0
  23. package/dist/src/chunking/interfaces.js +2 -0
  24. package/dist/src/commands/ConfigCommand.js +1 -9
  25. package/dist/src/commands/EvalCommand.d.ts +4 -3
  26. package/dist/src/commands/EvalCommand.js +85 -52
  27. package/dist/src/commands/IndexCommand.js +0 -22
  28. package/dist/src/commands/McpCommand.js +2 -8
  29. package/dist/src/commands/WebCommand.js +2 -5
  30. package/dist/src/config-error.d.ts +0 -4
  31. package/dist/src/config-error.js +0 -4
  32. package/dist/src/config-store.d.ts +1 -54
  33. package/dist/src/config-store.js +8 -69
  34. package/dist/src/config.d.ts +8 -17
  35. package/dist/src/config.js +35 -34
  36. package/dist/src/constants.d.ts +2 -28
  37. package/dist/src/constants.js +6 -28
  38. package/dist/src/container.d.ts +6 -0
  39. package/dist/src/container.js +12 -3
  40. package/dist/src/db/schema.d.ts +17 -0
  41. package/dist/src/db/schema.js +1 -6
  42. package/dist/src/declarations/DeclarationTree.d.ts +19 -0
  43. package/dist/src/declarations/DeclarationTree.js +66 -0
  44. package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
  45. package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
  46. package/dist/src/declarations/SourceLines.d.ts +37 -0
  47. package/dist/src/declarations/SourceLines.js +415 -0
  48. package/dist/src/dependency-traversal.d.ts +0 -6
  49. package/dist/src/dependency-traversal.js +0 -6
  50. package/dist/src/enriched-prompt.d.ts +0 -6
  51. package/dist/src/enriched-prompt.js +0 -6
  52. package/dist/src/error-record.d.ts +12 -0
  53. package/dist/src/error-record.js +248 -0
  54. package/dist/src/eval-corpus.d.ts +16 -0
  55. package/dist/src/eval-corpus.js +169 -0
  56. package/dist/src/eval-results.d.ts +6 -3
  57. package/dist/src/eval-results.js +309 -26
  58. package/dist/src/file-utils.d.ts +0 -5
  59. package/dist/src/file-utils.js +0 -5
  60. package/dist/src/graph.d.ts +0 -25
  61. package/dist/src/graph.js +0 -28
  62. package/dist/src/hnsw-settings.d.ts +3 -0
  63. package/dist/src/hnsw-settings.js +24 -0
  64. package/dist/src/interfaces/IAstParser.d.ts +2 -39
  65. package/dist/src/interfaces/IChunker.d.ts +3 -12
  66. package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
  67. package/dist/src/interfaces/ICorpusInspector.js +2 -0
  68. package/dist/src/interfaces/IDatabase.d.ts +2 -22
  69. package/dist/src/interfaces/IEmbedder.d.ts +0 -9
  70. package/dist/src/interfaces/ILogger.d.ts +1 -11
  71. package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
  72. package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
  73. package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
  74. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
  75. package/dist/src/interfaces/ISearcher.d.ts +16 -5
  76. package/dist/src/interrupt.js +0 -2
  77. package/dist/src/owner-path.d.ts +9 -10
  78. package/dist/src/owner-path.js +11 -23
  79. package/dist/src/project-scope.d.ts +0 -12
  80. package/dist/src/project-scope.js +0 -12
  81. package/dist/src/project-selector.d.ts +0 -5
  82. package/dist/src/project-selector.js +0 -11
  83. package/dist/src/retrieval-eval.d.ts +14 -6
  84. package/dist/src/retrieval-eval.js +345 -59
  85. package/dist/src/search/result-shaping.d.ts +4 -0
  86. package/dist/src/search/result-shaping.js +19 -0
  87. package/dist/src/services/AstParserService.d.ts +2 -62
  88. package/dist/src/services/AstParserService.js +25 -137
  89. package/dist/src/services/DatabaseService.d.ts +2 -7
  90. package/dist/src/services/DatabaseService.js +42 -29
  91. package/dist/src/services/EmbedderFactory.d.ts +0 -4
  92. package/dist/src/services/EmbedderFactory.js +0 -5
  93. package/dist/src/services/IndexerService.d.ts +0 -16
  94. package/dist/src/services/IndexerService.js +16 -45
  95. package/dist/src/services/InfinityEmbedder.d.ts +0 -21
  96. package/dist/src/services/InfinityEmbedder.js +0 -15
  97. package/dist/src/services/LoggerService.d.ts +7 -5
  98. package/dist/src/services/LoggerService.js +37 -41
  99. package/dist/src/services/McpServer.d.ts +22 -14
  100. package/dist/src/services/McpServer.js +70 -37
  101. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
  102. package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
  103. package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
  104. package/dist/src/services/NxMonorepoResolver.js +5 -56
  105. package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
  106. package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
  107. package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
  108. package/dist/src/services/ProjectDiscoveryService.js +0 -4
  109. package/dist/src/services/SearcherService.d.ts +9 -116
  110. package/dist/src/services/SearcherService.js +95 -176
  111. package/dist/src/services/VoyageEmbedder.d.ts +0 -6
  112. package/dist/src/services/VoyageEmbedder.js +0 -6
  113. package/dist/src/services/WebServerService.d.ts +1 -53
  114. package/dist/src/services/WebServerService.js +11 -57
  115. package/dist/src/services/http-retry.d.ts +0 -7
  116. package/dist/src/services/http-retry.js +1 -10
  117. package/dist/src/services/infinity-provisioning.d.ts +0 -74
  118. package/dist/src/services/infinity-provisioning.js +5 -88
  119. package/dist/src/services/legacy-ollama-index.d.ts +0 -16
  120. package/dist/src/services/legacy-ollama-index.js +0 -23
  121. package/dist/src/services/reranker.d.ts +0 -9
  122. package/dist/src/services/reranker.js +46 -12
  123. package/dist/src/setup.d.ts +11 -23
  124. package/dist/src/setup.js +98 -71
  125. package/dist/src/symbol-name.d.ts +0 -12
  126. package/dist/src/symbol-name.js +0 -13
  127. package/dist/src/symbol-rows.d.ts +0 -14
  128. package/dist/src/symbol-rows.js +0 -14
  129. package/dist/src/tool-call-log.d.ts +2 -0
  130. package/dist/src/tool-call-log.js +24 -0
  131. package/dist/src/tools/GetDependenciesTool.js +1 -1
  132. package/dist/src/tools/GetDependentsTool.js +1 -1
  133. package/dist/src/tools/SearchCodebaseTool.js +6 -3
  134. package/dist/src/tools/format-helpers.d.ts +0 -6
  135. package/dist/src/tools/format-helpers.js +87 -14
  136. package/dist/src/web/dashboard.d.ts +0 -8
  137. package/dist/src/web/dashboard.js +7 -10
  138. package/package.json +2 -2
  139. package/dist/src/services/AstChunker.d.ts +0 -42
  140. package/dist/src/services/AstChunker.js +0 -158
  141. package/docs/web-dashboard.md +0 -206
@@ -1,19 +1,30 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1
2
  import type { Config } from '../config';
2
3
  import type { ISearcher } from '../interfaces/ISearcher';
3
4
  import type { ILogger } from '../interfaces/ILogger';
4
5
  import type { IMcpToolHandler, ToolResponse } from '../interfaces/IMcpToolHandler';
5
- /**
6
- * The dispatch logic behind `CallToolRequestSchema` — pulled out so it is testable
7
- * without a live stdio transport. A rejecting handler becomes `Tool error: <message>`.
8
- */
9
6
  export declare function dispatchTool(tools: Map<string, IMcpToolHandler>, name: string, args: Record<string, unknown>, logger: ILogger): Promise<ToolResponse>;
7
+ export interface ToolCallRequest {
8
+ name: string;
9
+ arguments?: Record<string, unknown>;
10
+ _meta?: Record<string, unknown>;
11
+ }
12
+ export interface ToolCaller {
13
+ callId: number;
14
+ session: string | null;
15
+ client: {
16
+ name: string;
17
+ version: string;
18
+ } | null;
19
+ startupError?: Error;
20
+ }
21
+ export declare function handleToolCall(tools: Map<string, IMcpToolHandler>, req: ToolCallRequest, signal: AbortSignal, caller: ToolCaller, logger: ILogger): Promise<ToolResponse>;
22
+ export declare function createToolCallHandler(tools: Map<string, IMcpToolHandler>, server: Pick<Server, 'getClientVersion'>, identity: Pick<ToolCaller, 'session' | 'startupError'>, logger: ILogger): (req: {
23
+ params: ToolCallRequest;
24
+ }, extra: {
25
+ signal: AbortSignal;
26
+ }) => Promise<ToolResponse>;
10
27
  export declare function buildInstructions(t: number): string;
11
- /**
12
- * McpServer — thin orchestration layer that delegates tool handling
13
- * to individual IMcpToolHandler implementations (Open/Closed Principle).
14
- *
15
- * To add a new tool: implement IMcpToolHandler and register it in createTools().
16
- */
17
28
  export declare class McpServer {
18
29
  private searcher;
19
30
  private config;
@@ -22,9 +33,6 @@ export declare class McpServer {
22
33
  private readonly tools;
23
34
  constructor(searcher: ISearcher, config: Config, logger: ILogger);
24
35
  start(startupError?: Error): Promise<void>;
25
- /**
26
- * Factory method: creates and registers all tool handlers.
27
- * New tools are added here without modifying the dispatch logic above.
28
- */
36
+ createServer(startupError?: Error): Server;
29
37
  private createTools;
30
38
  }
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.McpServer = void 0;
37
37
  exports.dispatchTool = dispatchTool;
38
+ exports.handleToolCall = handleToolCall;
39
+ exports.createToolCallHandler = createToolCallHandler;
38
40
  exports.buildInstructions = buildInstructions;
39
41
  const fs = __importStar(require("fs"));
40
42
  const path = __importStar(require("path"));
@@ -42,6 +44,8 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
42
44
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
43
45
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
44
46
  const tools_1 = require("../tools");
47
+ const tool_call_log_1 = require("../tool-call-log");
48
+ const error_record_1 = require("../error-record");
45
49
  function readPackageVersion() {
46
50
  try {
47
51
  return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version ?? '0.0.0';
@@ -50,10 +54,6 @@ function readPackageVersion() {
50
54
  return '0.0.0';
51
55
  }
52
56
  }
53
- /**
54
- * The dispatch logic behind `CallToolRequestSchema` — pulled out so it is testable
55
- * without a live stdio transport. A rejecting handler becomes `Tool error: <message>`.
56
- */
57
57
  async function dispatchTool(tools, name, args, logger) {
58
58
  const handler = tools.get(name);
59
59
  if (!handler) {
@@ -63,11 +63,63 @@ async function dispatchTool(tools, name, args, logger) {
63
63
  return await handler.handle(args);
64
64
  }
65
65
  catch (err) {
66
+ logger.error('McpServer: tool handler error', { tool: name, err });
66
67
  const reason = err instanceof Error ? err.message : String(err);
67
- logger.error('McpServer: tool handler error', { tool: name, reason });
68
68
  return { isError: true, content: [{ type: 'text', text: `Tool error: ${reason}` }] };
69
69
  }
70
70
  }
71
+ function unavailableResponse(startupError) {
72
+ return {
73
+ isError: true,
74
+ content: [{
75
+ type: 'text',
76
+ text: `MCP server is unavailable: ${(0, error_record_1.errorMessage)(startupError)}\n\nCheck the log file and fix the issue, then restart the MCP server.`,
77
+ }],
78
+ };
79
+ }
80
+ function metaForLog(meta) {
81
+ if (!meta)
82
+ return null;
83
+ const { progressToken: _, ...rest } = meta;
84
+ return Object.keys(rest).length ? (0, tool_call_log_1.truncateForLog)(rest) : null;
85
+ }
86
+ async function handleToolCall(tools, req, signal, caller, logger) {
87
+ const t0 = Date.now();
88
+ const toolUseId = req._meta?.['claudecode/toolUseId'];
89
+ const context = typeof toolUseId === 'string' ? { callId: caller.callId, toolUseId } : { callId: caller.callId };
90
+ return logger.withContext(context, async () => {
91
+ const args = req.arguments ?? {};
92
+ const response = caller.startupError
93
+ ? unavailableResponse(caller.startupError)
94
+ : await dispatchTool(tools, req.name, args, logger);
95
+ const ms = Date.now() - t0;
96
+ if (!response.isError)
97
+ logger.debug('McpServer: tool handled', { tool: req.name, ms });
98
+ logger.info('tool call', {
99
+ tool: req.name,
100
+ args: (0, tool_call_log_1.truncateForLog)(args),
101
+ isError: response.isError ?? false,
102
+ responseChars: response.content.reduce((n, c) => n + (typeof c.text === 'string' ? c.text.length : 0), 0),
103
+ ms,
104
+ client: caller.client,
105
+ session: caller.session,
106
+ meta: metaForLog(req._meta),
107
+ ...(signal.aborted ? { cancelled: true } : {}),
108
+ });
109
+ return response;
110
+ });
111
+ }
112
+ function createToolCallHandler(tools, server, identity, logger) {
113
+ let nextCallId = 1;
114
+ return (req, extra) => {
115
+ const client = server.getClientVersion();
116
+ return handleToolCall(tools, req.params, extra.signal, {
117
+ ...identity,
118
+ callId: nextCallId++,
119
+ client: client ? { name: client.name, version: client.version } : null,
120
+ }, logger);
121
+ };
122
+ }
71
123
  function buildInstructions(t) {
72
124
  return `
73
125
  You have access to a local code intelligence server. Use these tools to search codebases by meaning, navigate import graphs, look up symbol definitions, and read source files.
@@ -91,7 +143,7 @@ Pick one workflow based on what you need:
91
143
  Every file or directory argument (\`file_path\`, \`callee_file\`, \`path\`) is workspace-relative, exactly as printed by search_codebase (e.g. \`my-app/src/index.ts\`; \`.\` is the workspace root) — copy paths between tools unchanged.
92
144
 
93
145
  - **list_projects** — call this first to see what projects are indexed, their exact names and their directories (\`path:\`).
94
- - **search_codebase** — semantic search across indexed source files. Returns file path, line number, and a code chunk. **Pass \`project\` whenever the target project is known or guessable from the question** — this workspace indexes many projects at once, and an unscoped query competes against all of them, which \`rerank\` cannot fix (reranking only reorders whatever the vector search already retrieved). Pass \`rerank: false\` for "what does X do" / "where is X" lookups described without an exact name — measurement shows reranking hurts these (-0.23 to -0.31 MRR@10). If you already know the exact symbol name, use \`find_symbol\` instead, not \`search_codebase\` — it is faster and exact, and the rerank setting does not matter there. Leave reranking at its default (omit \`rerank\`, or pass \`true\`) for conceptual/architectural queries, where it helps (+0.08 MRR@10).
146
+ - **search_codebase** — semantic search across indexed source files. Returns file path, line number, and a code chunk. **Pass \`project\` whenever the target project is known or guessable from the question** — this workspace indexes many projects at once, and an unscoped query competes against all of them, which \`rerank\` cannot fix (reranking only reorders whatever the vector search already retrieved). Pass \`rerank: false\` for "what does X do" / "where is X" lookups described without an exact name — measurement shows reranking hurts these (-0.13 MRR@10, hit@5 68% → 50%). If you already know the exact symbol name, use \`find_symbol\` instead, not \`search_codebase\` — it is faster and exact, and the rerank setting does not matter there. For conceptual/architectural queries reranking is roughly neutral (-0.05 MRR@10, not significant), so omitting \`rerank\` is fine.
95
147
  - **read_file_range** — read a specific line range from any file under the workspace, indexed or not.
96
148
  - **get_dependencies** — show what files a given file imports (direct or transitive). Use to understand what a file depends on.
97
149
  - **get_dependents** — show which files import a given file (reverse graph). Use before refactoring to find all callers.
@@ -106,7 +158,7 @@ Every file or directory argument (\`file_path\`, \`callee_file\`, \`path\`) is w
106
158
  1. Use \`list_projects\` to discover available projects before filtering by \`project\`.
107
159
  2. Use \`search_codebase\` to find relevant code by meaning. **Always pass \`project\` when the question names or implies one** — an unscoped search ranks candidates across every indexed project together, so an unrelated project's weak match can outrank the right project's real one. If your query uses an ambiguous term (e.g. "subscription" could mean pub/sub or billing), pass a lower \`threshold\` (e.g. \`0.40–0.45\`) to filter out weak matches.
108
160
  3. If a result looks **truncated or incomplete**, call \`read_file_range\` with the returned \`file_path\` and \`start_line\`.
109
- 4. For exploratory searches where results span chunk boundaries, pass \`include_neighbors: true\`.
161
+ 4. For exploratory searches where results span chunk boundaries, pass \`include_neighbors: true\`. Results hold at most 2 chunks per file by default; pass \`per_file\` (e.g. \`5\`) when several answers are expected in one file, such as several methods of one class.
110
162
  5. To immediately see a file's imports and callers without extra tool calls, pass \`include_graph_context: true\` to \`search_codebase\`. Use this when doing a quick structural survey; switch to \`get_dependencies\`/\`get_dependents\` for full transitive graphs.
111
163
  6. Use \`find_symbol\` when you know the exact name — faster and exact, unlike semantic search. Use a qualified name (\`Class.method\`) to disambiguate same-named methods of different classes; a bare name searches every container. When you also want surrounding context (callers' intent, nearby helpers, how it's described elsewhere), call \`search_codebase\` in parallel rather than after — the two tools answer different questions and neither blocks the other. Hits marked \`(grep — not in index; may be stale or imprecise)\` come from a fallback text search, not the index — treat them as less reliable. After locating a symbol, use \`get_callers\` to find all call sites — faster and more precise than grep. For a NestJS GraphQL operation, resolve field or type, pass the matching \`graphql_query\`/\`graphql_mutation\`/\`graphql_subscription\`/\`graphql_field\`/\`graphql_type\` kind to look it up by the name the schema exposes, not the TypeScript declaration name.
112
164
  7. Use \`list_files\` to explore project structure before diving into files. Use \`read_file\` to read a complete file.
@@ -135,7 +187,7 @@ Every file or directory argument (\`file_path\`, \`callee_file\`, \`path\`) is w
135
187
 
136
188
  ## Dependency graph
137
189
 
138
- \`get_dependencies\` and \`get_dependents\` take \`file_path\` alone and list edges in every project. A path that names no file is an error, never an empty result. Pass \`transitive: true\` for the full dependency chain. depType values: \`import\` | \`import-default\` | \`import-namespace\` | \`import-side-effect\` | \`reexport\` | \`reexport-namespace\`. \`fromSymbol\` — the top-level class or function in the importing file that uses this import (null if ambiguous or not tracked).
190
+ \`get_dependencies\` and \`get_dependents\` take \`file_path\` alone and list edges in every project. A path that names no file is an error, never an empty result. Pass \`transitive: true\` for the full dependency chain. The direct section prints one line per file, with the names imported from/by it (\`default\` / \`* as namespace\` / \`side-effect\` standing in for a nameless import, re-exports grouped after plain imports under one \`re-export \` prefix, \`export *\` for a namespace re-export); \`get_dependents\` also lists the enclosing declarations (\`· in \`PlansPage\`\`) that use the import — only those resolved unambiguously (a binding used in 2+ declarations yields none), so the list can be partial or absent.
139
191
 
140
192
  If \`get_dependents\` returns 0 results, add \`symbol\` (the exported name you care about) — this triggers a grep fallback that finds runtime usages in DI containers, string references, and dynamic lookups that the import graph misses.
141
193
 
@@ -144,12 +196,6 @@ If \`get_dependents\` returns 0 results, add \`symbol\` (the exported name you c
144
196
  Lower score = better match (cosine distance). Results above the configured threshold are filtered out automatically. Treat scores above 0.50 as weak hints — verify with \`read_file_range\` or \`Grep\`. Pass \`threshold\` to \`search_codebase\` to override per-query: \`0.40–0.45\` for precise technical queries (known term, exact concept); \`${t}\` default (broad but reliable). Avoid \`0.35\` — too strict for DSL files (GraphQL, Prisma) and infrastructure code.
145
197
  `.trim();
146
198
  }
147
- /**
148
- * McpServer — thin orchestration layer that delegates tool handling
149
- * to individual IMcpToolHandler implementations (Open/Closed Principle).
150
- *
151
- * To add a new tool: implement IMcpToolHandler and register it in createTools().
152
- */
153
199
  class McpServer {
154
200
  searcher;
155
201
  config;
@@ -165,35 +211,22 @@ class McpServer {
165
211
  }
166
212
  async start(startupError) {
167
213
  if (startupError) {
168
- this.logger.error('McpServer: startup failed, server will report error on every tool call', { reason: startupError.message });
214
+ this.logger.error('McpServer: startup failed, server will report error on every tool call', { err: startupError });
169
215
  }
216
+ await this.createServer(startupError).connect(new stdio_js_1.StdioServerTransport());
217
+ this.logger.info('McpServer: started', { version: this.version });
218
+ }
219
+ createServer(startupError) {
170
220
  const server = new index_js_1.Server({ name: 'local-rag', version: this.version }, { capabilities: { tools: {} }, instructions: buildInstructions(this.config.search.similarityThreshold) });
171
221
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
172
222
  tools: [...this.tools.values()].map(t => t.definition()),
173
223
  }));
174
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
175
- if (startupError) {
176
- return {
177
- isError: true,
178
- content: [{
179
- type: 'text',
180
- text: `MCP server is unavailable: ${startupError.message}\n\nCheck the log file and fix the issue, then restart the MCP server.`,
181
- }],
182
- };
183
- }
184
- const t0 = Date.now();
185
- const result = await dispatchTool(this.tools, req.params.name, (req.params.arguments ?? {}), this.logger);
186
- if (!result.isError)
187
- this.logger.debug('McpServer: tool handled', { tool: req.params.name, ms: Date.now() - t0 });
188
- return result;
189
- });
190
- await server.connect(new stdio_js_1.StdioServerTransport());
191
- this.logger.info('McpServer: started', { version: this.version });
224
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, createToolCallHandler(this.tools, server, {
225
+ session: (0, tool_call_log_1.resolveSession)(process.env),
226
+ startupError,
227
+ }, this.logger));
228
+ return server;
192
229
  }
193
- /**
194
- * Factory method: creates and registers all tool handlers.
195
- * New tools are added here without modifying the dispatch logic above.
196
- */
197
230
  createTools() {
198
231
  const handlers = [
199
232
  new tools_1.ListProjectsTool(this.searcher),
@@ -6,57 +6,13 @@ export interface WorkspaceInfo {
6
6
  rootTsConfigPath: string | null;
7
7
  projects: DiscoveredProject[];
8
8
  }
9
- /**
10
- * Expand discovery units into the leaf projects to index: a nested monorepo
11
- * unit (with `children`) becomes its sub-projects; an ordinary project stays
12
- * itself. Shared by IndexCommand (indexing loop) and SearcherService (name→path
13
- * resolution) so both agree on the leaf set.
14
- */
15
9
  export declare function toLeafProjects(projects: DiscoveredProject[]): DiscoveredProject[];
16
- /**
17
- * Project discovery that is monorepo-aware.
18
- *
19
- * When a monorepo is detected (via the resolver), it uses the resolver
20
- * to discover projects. Otherwise, it falls back to the original
21
- * flat-workspace discovery (directories with package.json). Cross-project
22
- * import resolution relies on the tsconfig `paths` attached via
23
- * `rootTsConfigPath` — no separate scope map is built.
24
- *
25
- * Single Responsibility: only discovers projects and workspace metadata.
26
- * Does not index or search.
27
- *
28
- * Implements {@link IProjectDiscovery} so it can be wired as the default
29
- * discovery in the container. `discover()` returns one entry per top-level
30
- * project: when the root itself is a monorepo, each entry carries the
31
- * workspace-wide `rootTsConfigPath`; under a flat root, an ordinary project
32
- * gets `rootTsConfigPath: null`, while a top-level directory that is itself a
33
- * nested monorepo is returned as a single unit with expanded `children` (see
34
- * `toLeafProjects`). `analyzeWorkspace()` exposes the full workspace metadata
35
- * (monorepo flag, root tsconfig, project list).
36
- */
37
10
  export declare class MonorepoAwareDiscoveryService implements IProjectDiscovery {
38
11
  private resolvers;
39
12
  private logger;
40
- /** Discovery used when no monorepo is detected. Defaults to flat package.json scan. */
41
13
  private flatDiscovery;
42
- constructor(resolvers: IMonorepoResolver[], logger: ILogger,
43
- /** Discovery used when no monorepo is detected. Defaults to flat package.json scan. */
44
- flatDiscovery?: IProjectDiscovery);
45
- /**
46
- * IProjectDiscovery contract: enumerate projects in the workspace.
47
- * Attaches the workspace-wide root tsconfig to every project so the
48
- * indexer can resolve cross-project path aliases.
49
- */
14
+ constructor(resolvers: IMonorepoResolver[], logger: ILogger, flatDiscovery?: IProjectDiscovery);
50
15
  discover(rootDir: string): DiscoveredProject[];
51
- /**
52
- * If `project`'s directory is itself a monorepo (resolver detects it),
53
- * return it with expanded `children` named `<repo>/<sub-project>`, each
54
- * carrying the repo's workspace-wide tsconfig. Otherwise return it unchanged.
55
- */
56
16
  private expandIfNestedMonorepo;
57
- /**
58
- * Analyze the workspace root and return full workspace information:
59
- * monorepo detection flag, root tsconfig path, and project list.
60
- */
61
17
  analyzeWorkspace(rootDir: string): WorkspaceInfo;
62
18
  }
@@ -3,71 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MonorepoAwareDiscoveryService = void 0;
4
4
  exports.toLeafProjects = toLeafProjects;
5
5
  const ProjectDiscoveryService_1 = require("./ProjectDiscoveryService");
6
- /**
7
- * Expand discovery units into the leaf projects to index: a nested monorepo
8
- * unit (with `children`) becomes its sub-projects; an ordinary project stays
9
- * itself. Shared by IndexCommand (indexing loop) and SearcherService (name→path
10
- * resolution) so both agree on the leaf set.
11
- */
12
6
  function toLeafProjects(projects) {
13
7
  return projects.flatMap(p => (p.children && p.children.length > 0 ? p.children : [p]));
14
8
  }
15
- /**
16
- * Project discovery that is monorepo-aware.
17
- *
18
- * When a monorepo is detected (via the resolver), it uses the resolver
19
- * to discover projects. Otherwise, it falls back to the original
20
- * flat-workspace discovery (directories with package.json). Cross-project
21
- * import resolution relies on the tsconfig `paths` attached via
22
- * `rootTsConfigPath` — no separate scope map is built.
23
- *
24
- * Single Responsibility: only discovers projects and workspace metadata.
25
- * Does not index or search.
26
- *
27
- * Implements {@link IProjectDiscovery} so it can be wired as the default
28
- * discovery in the container. `discover()` returns one entry per top-level
29
- * project: when the root itself is a monorepo, each entry carries the
30
- * workspace-wide `rootTsConfigPath`; under a flat root, an ordinary project
31
- * gets `rootTsConfigPath: null`, while a top-level directory that is itself a
32
- * nested monorepo is returned as a single unit with expanded `children` (see
33
- * `toLeafProjects`). `analyzeWorkspace()` exposes the full workspace metadata
34
- * (monorepo flag, root tsconfig, project list).
35
- */
36
9
  class MonorepoAwareDiscoveryService {
37
10
  resolvers;
38
11
  logger;
39
12
  flatDiscovery;
40
- constructor(resolvers, logger,
41
- /** Discovery used when no monorepo is detected. Defaults to flat package.json scan. */
42
- flatDiscovery = new ProjectDiscoveryService_1.ProjectDiscoveryService()) {
13
+ constructor(resolvers, logger, flatDiscovery = new ProjectDiscoveryService_1.ProjectDiscoveryService()) {
43
14
  this.resolvers = resolvers;
44
15
  this.logger = logger;
45
16
  this.flatDiscovery = flatDiscovery;
46
17
  }
47
- /**
48
- * IProjectDiscovery contract: enumerate projects in the workspace.
49
- * Attaches the workspace-wide root tsconfig to every project so the
50
- * indexer can resolve cross-project path aliases.
51
- */
52
18
  discover(rootDir) {
53
19
  const workspace = this.analyzeWorkspace(rootDir);
54
- // Case A: workspaceRoot itself is a monorepo — attach the workspace-wide
55
- // tsconfig to every project (original behavior).
56
20
  if (workspace.isMonorepo) {
57
21
  return workspace.projects.map(p => ({ ...p, rootTsConfigPath: workspace.rootTsConfigPath }));
58
22
  }
59
- // Case B: flat workspaceRoot — a top-level project directory may itself be a
60
- // nested monorepo (e.g. an Nx repo). Expand such a directory into its
61
- // sub-projects as `children`, keeping the repo a single selectable unit.
62
23
  return workspace.projects.map(p => this.expandIfNestedMonorepo(p));
63
24
  }
64
- /**
65
- * If `project`'s directory is itself a monorepo (resolver detects it),
66
- * return it with expanded `children` named `<repo>/<sub-project>`, each
67
- * carrying the repo's workspace-wide tsconfig. Otherwise return it unchanged.
68
- */
69
25
  expandIfNestedMonorepo(project) {
70
- // Ordinary flat project — preserve the original contract (null root tsconfig).
71
26
  const flat = { ...project, rootTsConfigPath: null };
72
27
  const resolver = this.resolvers.find(r => r.isMonorepo(project.path));
73
28
  if (!resolver)
@@ -89,10 +44,6 @@ class MonorepoAwareDiscoveryService {
89
44
  });
90
45
  return { ...project, children };
91
46
  }
92
- /**
93
- * Analyze the workspace root and return full workspace information:
94
- * monorepo detection flag, root tsconfig path, and project list.
95
- */
96
47
  analyzeWorkspace(rootDir) {
97
48
  for (const resolver of this.resolvers) {
98
49
  if (resolver.isMonorepo(rootDir)) {
@@ -112,7 +63,6 @@ class MonorepoAwareDiscoveryService {
112
63
  return { isMonorepo: true, rootTsConfigPath, projects };
113
64
  }
114
65
  }
115
- // Fallback: flat workspace (original behavior) — delegated to the default discovery.
116
66
  this.logger.debug('MonorepoAwareDiscovery: no monorepo detected, using flat discovery', { rootDir });
117
67
  const projects = this.flatDiscovery.discover(rootDir);
118
68
  return {
@@ -1,40 +1,11 @@
1
1
  import type { IMonorepoResolver, MonorepoProject } from '../interfaces/IMonorepoResolver';
2
2
  import type { ILogger } from '../interfaces';
3
- /**
4
- * Nx monorepo resolver.
5
- *
6
- * Detection: presence of `nx.json` at the workspace root.
7
- *
8
- * Project discovery strategies (results deduplicated by absolute path):
9
- * 1. Read `workspace.json` or `angular.json` → projects map
10
- * 2. Scan standard Nx directories (`apps/`, `libs/`, `packages/`) for
11
- * `project.json`, then `package.json` (only when strategy 1 found nothing)
12
- * 3. `tsconfig.base.json` `paths` — root-level libs referenced only by path
13
- * alias (e.g. `contracts/`, `interfaces/`) that the directory scan misses
14
- * 4. Root-level fallback: treat the workspace root itself as a project
15
- * (standalone Nx setup) when nothing else matched
16
- *
17
- * tsconfig resolution:
18
- * - Returns `tsconfig.base.json` at workspace root (Nx convention)
19
- * - Falls back to `tsconfig.json` at workspace root
20
- *
21
- * Cross-project import resolution is handled by the AST parser via the
22
- * workspace-wide tsconfig `paths` (tsconfig.base.json), so no scope map is built here.
23
- */
24
3
  export declare class NxMonorepoResolver implements IMonorepoResolver {
25
4
  private logger;
26
5
  constructor(logger: ILogger);
27
6
  isMonorepo(rootDir: string): boolean;
28
7
  discoverProjects(rootDir: string): MonorepoProject[];
29
- /**
30
- * Discover projects from `tsconfig.base.json` `compilerOptions.paths`. Each
31
- * alias (e.g. `@scope/interfaces` → `interfaces/src/index.ts`) resolves to a
32
- * lib root (the path before `/src/`), which for real Nx libs contains a
33
- * project.json. Aliases pointing at non-projects (no project.json/package.json,
34
- * e.g. `@env` → `environments/environment.ts`) are skipped.
35
- */
36
8
  private discoverFromTsconfigPaths;
37
- /** Parse JSON that may contain comments / trailing commas (tsconfig style). */
38
9
  private parseJsonc;
39
10
  getRootTsConfigPath(rootDir: string): string | null;
40
11
  private readWorkspaceJson;
@@ -37,27 +37,6 @@ exports.NxMonorepoResolver = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const file_utils_1 = require("../file-utils");
40
- /**
41
- * Nx monorepo resolver.
42
- *
43
- * Detection: presence of `nx.json` at the workspace root.
44
- *
45
- * Project discovery strategies (results deduplicated by absolute path):
46
- * 1. Read `workspace.json` or `angular.json` → projects map
47
- * 2. Scan standard Nx directories (`apps/`, `libs/`, `packages/`) for
48
- * `project.json`, then `package.json` (only when strategy 1 found nothing)
49
- * 3. `tsconfig.base.json` `paths` — root-level libs referenced only by path
50
- * alias (e.g. `contracts/`, `interfaces/`) that the directory scan misses
51
- * 4. Root-level fallback: treat the workspace root itself as a project
52
- * (standalone Nx setup) when nothing else matched
53
- *
54
- * tsconfig resolution:
55
- * - Returns `tsconfig.base.json` at workspace root (Nx convention)
56
- * - Falls back to `tsconfig.json` at workspace root
57
- *
58
- * Cross-project import resolution is handled by the AST parser via the
59
- * workspace-wide tsconfig `paths` (tsconfig.base.json), so no scope map is built here.
60
- */
61
40
  class NxMonorepoResolver {
62
41
  logger;
63
42
  constructor(logger) {
@@ -67,15 +46,12 @@ class NxMonorepoResolver {
67
46
  return fs.existsSync(path.join(rootDir, 'nx.json'));
68
47
  }
69
48
  discoverProjects(rootDir) {
70
- // Deduplicate across strategies by absolute project path.
71
49
  const byPath = new Map();
72
50
  const add = (p) => { if (p && !byPath.has(p.path))
73
51
  byPath.set(p.path, p); };
74
- // Strategy 1: workspace.json / angular.json (authoritative when present)
75
52
  const workspaceProjects = this.readWorkspaceJson(rootDir);
76
53
  for (const p of workspaceProjects)
77
54
  add(p);
78
- // Strategy 2 & 3: scan standard directories (only when no workspace.json map)
79
55
  if (byPath.size === 0) {
80
56
  for (const dir of ['apps', 'libs', 'packages']) {
81
57
  const fullDir = path.join(rootDir, dir);
@@ -86,13 +62,8 @@ class NxMonorepoResolver {
86
62
  add(p);
87
63
  }
88
64
  }
89
- // Strategy 4: tsconfig.base.json `paths` — captures libs that live at the
90
- // repo root (e.g. `contracts/`, `interfaces/`) referenced by path aliases,
91
- // which the apps/libs/packages scan misses. Only real Nx projects (with a
92
- // project.json/package.json) are added; dedup handles overlap with the scan.
93
65
  for (const p of this.discoverFromTsconfigPaths(rootDir))
94
66
  add(p);
95
- // Root-level fallback (standalone Nx setup)
96
67
  if (byPath.size === 0) {
97
68
  const rootProject = this.tryReadProject(rootDir, rootDir);
98
69
  if (rootProject)
@@ -102,13 +73,6 @@ class NxMonorepoResolver {
102
73
  this.logger.debug('NxMonorepoResolver: discovered projects', { count: projects.length });
103
74
  return projects;
104
75
  }
105
- /**
106
- * Discover projects from `tsconfig.base.json` `compilerOptions.paths`. Each
107
- * alias (e.g. `@scope/interfaces` → `interfaces/src/index.ts`) resolves to a
108
- * lib root (the path before `/src/`), which for real Nx libs contains a
109
- * project.json. Aliases pointing at non-projects (no project.json/package.json,
110
- * e.g. `@env` → `environments/environment.ts`) are skipped.
111
- */
112
76
  discoverFromTsconfigPaths(rootDir) {
113
77
  const tsconfigPath = this.getRootTsConfigPath(rootDir);
114
78
  if (!tsconfigPath)
@@ -118,7 +82,7 @@ class NxMonorepoResolver {
118
82
  json = this.parseJsonc(fs.readFileSync(tsconfigPath, 'utf-8'));
119
83
  }
120
84
  catch (err) {
121
- this.logger.warn('NxMonorepoResolver: failed to parse tsconfig paths', { file: tsconfigPath, reason: err.message });
85
+ this.logger.warn('NxMonorepoResolver: failed to parse tsconfig paths', { file: tsconfigPath, err });
122
86
  return [];
123
87
  }
124
88
  const co = json?.compilerOptions ?? {};
@@ -134,13 +98,12 @@ class NxMonorepoResolver {
134
98
  const srcIdx = target.indexOf('/src/');
135
99
  const relRoot = srcIdx >= 0 ? target.slice(0, srcIdx) : path.dirname(target);
136
100
  const libRoot = path.resolve(rootDir, baseUrl, relRoot);
137
- const project = this.tryReadProject(libRoot, rootDir); // null unless a real project marker exists
101
+ const project = this.tryReadProject(libRoot, rootDir);
138
102
  if (project)
139
103
  projects.push(project);
140
104
  }
141
105
  return projects;
142
106
  }
143
- /** Parse JSON that may contain comments / trailing commas (tsconfig style). */
144
107
  parseJsonc(text) {
145
108
  try {
146
109
  return JSON.parse(text);
@@ -154,17 +117,14 @@ class NxMonorepoResolver {
154
117
  }
155
118
  }
156
119
  getRootTsConfigPath(rootDir) {
157
- // Nx convention: tsconfig.base.json at workspace root
158
120
  const basePath = path.join(rootDir, 'tsconfig.base.json');
159
121
  if (fs.existsSync(basePath))
160
122
  return basePath;
161
- // Fallback: tsconfig.json at workspace root
162
123
  const rootPath = path.join(rootDir, 'tsconfig.json');
163
124
  if (fs.existsSync(rootPath))
164
125
  return rootPath;
165
126
  return null;
166
127
  }
167
- // ── Private helpers ─────────────────────────────────────────────────────────
168
128
  readWorkspaceJson(rootDir) {
169
129
  const projects = [];
170
130
  for (const filename of ['workspace.json', 'angular.json']) {
@@ -175,9 +135,6 @@ class NxMonorepoResolver {
175
135
  const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
176
136
  const projectsMap = content.projects ?? {};
177
137
  for (const [name, value] of Object.entries(projectsMap)) {
178
- // value can be a string (path) or an object with a `root` property.
179
- // Guard per-entry: a malformed entry (object without `root`) must not
180
- // abort discovery of the remaining projects in this file.
181
138
  const rawRoot = typeof value === 'string'
182
139
  ? value
183
140
  : value?.root;
@@ -196,7 +153,7 @@ class NxMonorepoResolver {
196
153
  catch (err) {
197
154
  this.logger.warn('NxMonorepoResolver: failed to parse workspace file', {
198
155
  file: filePath,
199
- reason: err.message,
156
+ err,
200
157
  });
201
158
  }
202
159
  }
@@ -222,14 +179,12 @@ class NxMonorepoResolver {
222
179
  projects.push(project);
223
180
  continue;
224
181
  }
225
- // Check for nested grouping directories (e.g. libs/shared/utils)
226
182
  const nested = this.scanDirectory(projectPath, defaultType, rootDir);
227
183
  projects.push(...nested);
228
184
  }
229
185
  return projects;
230
186
  }
231
187
  tryReadProject(projectPath, rootDir, defaultType = 'lib') {
232
- // Check for project.json (Nx project marker)
233
188
  const projectJsonPath = path.join(projectPath, 'project.json');
234
189
  const packageJsonPath = path.join(projectPath, 'package.json');
235
190
  const hasProjectJson = fs.existsSync(projectJsonPath);
@@ -239,7 +194,6 @@ class NxMonorepoResolver {
239
194
  let name = path.relative(rootDir, projectPath).replace(/\\/g, '/');
240
195
  let type = defaultType;
241
196
  let packageName = null;
242
- // Read project.json for name and type
243
197
  if (hasProjectJson) {
244
198
  try {
245
199
  const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
@@ -253,14 +207,12 @@ class NxMonorepoResolver {
253
207
  catch (err) {
254
208
  this.logger.warn('NxMonorepoResolver: failed to parse project.json', {
255
209
  file: projectJsonPath,
256
- reason: err.message,
210
+ err,
257
211
  });
258
212
  }
259
213
  }
260
- // Read package.json for npm scope
261
214
  if (hasPackageJson) {
262
215
  packageName = this.readPackageName(projectPath);
263
- // If no project.json name, use package name without scope
264
216
  if (!hasProjectJson && packageName) {
265
217
  const shortName = packageName.replace(/^@[^/]+\//, '');
266
218
  name = shortName;
@@ -281,7 +233,6 @@ class NxMonorepoResolver {
281
233
  }
282
234
  }
283
235
  inferProjectType(projectPath, name) {
284
- // Check project.json
285
236
  const projectJsonPath = path.join(projectPath, 'project.json');
286
237
  if (fs.existsSync(projectJsonPath)) {
287
238
  try {
@@ -291,15 +242,13 @@ class NxMonorepoResolver {
291
242
  if (projectJson.projectType === 'library')
292
243
  return 'lib';
293
244
  }
294
- catch { /* fall through */ }
245
+ catch { }
295
246
  }
296
- // Infer from path
297
247
  const rel = projectPath.replace(/\\/g, '/');
298
248
  if (rel.includes('/apps/') || rel.includes('/app/'))
299
249
  return 'app';
300
250
  if (rel.includes('/libs/') || rel.includes('/lib/') || rel.includes('/packages/'))
301
251
  return 'lib';
302
- // Infer from name
303
252
  if (name.includes('lib') || name.includes('shared') || name.includes('util'))
304
253
  return 'lib';
305
254
  return 'app';
@@ -5,12 +5,6 @@ export interface EmbedderDeps {
5
5
  fetchImpl?: typeof fetch;
6
6
  baseDelayMs?: number;
7
7
  }
8
- /**
9
- * Embedder for any server exposing an OpenAI-style `/embeddings` endpoint:
10
- * OpenAI cloud, Infinity, TEI, vLLM, LM Studio, LocalAI. One request carries all
11
- * inputs (`{ input: string[], model, dimensions? }`) and returns
12
- * `{ data: [{ embedding, index }] }`, re-ordered to input order.
13
- */
14
8
  export declare class OpenAICompatibleEmbedder implements IEmbedder {
15
9
  protected config: Config;
16
10
  private logger;