codesearch-mcp 0.0.17-alpha.1 → 0.0.18-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 (83) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +20 -0
  3. package/dist/app.js +34 -10
  4. package/dist/src/commands/ConfigCommand.d.ts +1 -0
  5. package/dist/src/commands/ConfigCommand.js +84 -4
  6. package/dist/src/commands/IndexCommand.js +22 -7
  7. package/dist/src/commands/McpCommand.js +4 -3
  8. package/dist/src/commands/WebCommand.d.ts +8 -0
  9. package/dist/src/commands/WebCommand.js +56 -0
  10. package/dist/src/config-store.d.ts +43 -0
  11. package/dist/src/config-store.js +92 -27
  12. package/dist/src/config.js +16 -17
  13. package/dist/src/constants.d.ts +2 -1
  14. package/dist/src/constants.js +6 -4
  15. package/dist/src/container.d.ts +10 -6
  16. package/dist/src/container.js +6 -3
  17. package/dist/src/graph.d.ts +60 -0
  18. package/dist/src/graph.js +239 -120
  19. package/dist/src/help.js +15 -1
  20. package/dist/src/interfaces/IAstParser.d.ts +6 -3
  21. package/dist/src/interfaces/IDatabase.d.ts +13 -0
  22. package/dist/src/interfaces/IDatabase.js +2 -0
  23. package/dist/src/interfaces/IEmbedder.d.ts +1 -0
  24. package/dist/src/interfaces/ILogger.d.ts +10 -0
  25. package/dist/src/interfaces/ILogger.js +2 -0
  26. package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
  27. package/dist/src/interfaces/IMcpToolHandler.js +2 -0
  28. package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
  29. package/dist/src/interfaces/IMonorepoResolver.js +2 -0
  30. package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
  31. package/dist/src/interfaces/IProjectDiscovery.js +2 -0
  32. package/dist/src/interfaces/ISearcher.d.ts +36 -0
  33. package/dist/src/interfaces/ISearcher.js +2 -0
  34. package/dist/src/interfaces/index.d.ts +7 -0
  35. package/dist/src/interfaces/index.js +2 -0
  36. package/dist/src/project-selector.d.ts +1 -1
  37. package/dist/src/services/AstParserService.d.ts +4 -6
  38. package/dist/src/services/AstParserService.js +29 -13
  39. package/dist/src/services/DatabaseService.d.ts +2 -1
  40. package/dist/src/services/IndexerService.d.ts +6 -8
  41. package/dist/src/services/IndexerService.js +5 -8
  42. package/dist/src/services/LoggerService.d.ts +2 -1
  43. package/dist/src/services/McpServer.d.ts +16 -7
  44. package/dist/src/services/McpServer.js +54 -367
  45. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
  46. package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
  47. package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
  48. package/dist/src/services/NxMonorepoResolver.js +308 -0
  49. package/dist/src/services/OllamaEmbedder.d.ts +5 -2
  50. package/dist/src/services/OllamaEmbedder.js +9 -2
  51. package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
  52. package/dist/src/services/ProjectDiscoveryService.js +52 -0
  53. package/dist/src/services/SearcherService.d.ts +39 -6
  54. package/dist/src/services/SearcherService.js +151 -40
  55. package/dist/src/services/WebServerService.d.ts +72 -0
  56. package/dist/src/services/WebServerService.js +247 -0
  57. package/dist/src/setup.js +7 -7
  58. package/dist/src/tools/FindSymbolTool.d.ts +8 -0
  59. package/dist/src/tools/FindSymbolTool.js +45 -0
  60. package/dist/src/tools/GetCallersTool.d.ts +8 -0
  61. package/dist/src/tools/GetCallersTool.js +42 -0
  62. package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
  63. package/dist/src/tools/GetDependenciesTool.js +38 -0
  64. package/dist/src/tools/GetDependentsTool.d.ts +8 -0
  65. package/dist/src/tools/GetDependentsTool.js +57 -0
  66. package/dist/src/tools/ListFilesTool.d.ts +8 -0
  67. package/dist/src/tools/ListFilesTool.js +39 -0
  68. package/dist/src/tools/ListProjectsTool.d.ts +8 -0
  69. package/dist/src/tools/ListProjectsTool.js +44 -0
  70. package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
  71. package/dist/src/tools/ReadFileRangeTool.js +44 -0
  72. package/dist/src/tools/ReadFileTool.d.ts +8 -0
  73. package/dist/src/tools/ReadFileTool.js +42 -0
  74. package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
  75. package/dist/src/tools/SearchCodebaseTool.js +81 -0
  76. package/dist/src/tools/format-helpers.d.ts +8 -0
  77. package/dist/src/tools/format-helpers.js +57 -0
  78. package/dist/src/tools/index.d.ts +9 -0
  79. package/dist/src/tools/index.js +21 -0
  80. package/dist/src/web/dashboard.d.ts +9 -0
  81. package/dist/src/web/dashboard.js +337 -0
  82. package/docs/web-dashboard.md +187 -0
  83. package/package.json +5 -2
@@ -0,0 +1,36 @@
1
+ import type { SearchResult, NeighborResult, GraphContext, DependencyRow, ProjectRow, GrepMatch, SymbolKind, SymbolHit, CallerRow, FileRangeResult } from '../services/SearcherService';
2
+ /**
3
+ * Searcher abstraction — McpServer depends on this interface
4
+ * rather than the concrete SearcherService.
5
+ */
6
+ export interface ISearcher {
7
+ prepareQuery(raw: string): Promise<{
8
+ query: string;
9
+ translated: boolean;
10
+ }>;
11
+ listProjects(): Promise<ProjectRow[]>;
12
+ getProjectEdges(): Promise<{
13
+ from: string;
14
+ to: string;
15
+ }[]>;
16
+ searchChunks(query: string, limit: number, project: string | null, threshold?: number): Promise<SearchResult[]>;
17
+ fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
18
+ fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
19
+ getDependencies(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
20
+ getDependents(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
21
+ getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
22
+ grepSymbol(symbol: string, project: string, excludeFile: string): Promise<{
23
+ matches: GrepMatch[];
24
+ truncated: boolean;
25
+ }>;
26
+ findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
27
+ hits: SymbolHit[];
28
+ truncated: boolean;
29
+ }>;
30
+ listFiles(relativePath: string, project: string, depth: number, pattern: string | null): {
31
+ tree: string;
32
+ error?: string;
33
+ };
34
+ readFileAbsolute(absPath: string, maxLines: number): Promise<FileRangeResult>;
35
+ readFileRange(filePath: string, startLine: number, endLine: number): Promise<FileRangeResult>;
36
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export type { IEmbedder } from './IEmbedder';
2
+ export type { IAstParser, DepEdge, SymbolEntry, CallEdge, FileAnalysis } from './IAstParser';
3
+ export type { ILogger } from './ILogger';
4
+ export type { IDatabase } from './IDatabase';
5
+ export type { ISearcher } from './ISearcher';
6
+ export type { IProjectDiscovery, DiscoveredProject } from './IProjectDiscovery';
7
+ export type { IMcpToolHandler, ToolDefinition, ToolResponse } from './IMcpToolHandler';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -7,7 +7,7 @@ export declare class ProjectSelector {
7
7
  private included;
8
8
  private skipped;
9
9
  constructor(workspaceDir: string);
10
- select(allProjects: Project[], forceSelect: boolean): Promise<Project[]>;
10
+ select<T extends Project>(allProjects: T[], forceSelect: boolean): Promise<T[]>;
11
11
  private printAlreadySelected;
12
12
  private promptForSelection;
13
13
  private promptForSkip;
@@ -1,12 +1,10 @@
1
- import type { IAstParser, DepEdge, FileAnalysis } from '../interfaces/IAstParser';
2
- import { LoggerService } from './LoggerService';
1
+ import type { IAstParser, DepEdge, FileAnalysis, AnalyzeProjectOpts } from '../interfaces/IAstParser';
2
+ import type { ILogger } from '../interfaces';
3
3
  export declare class AstParserService implements IAstParser {
4
4
  private logger;
5
5
  private morphProjects;
6
- constructor(logger?: LoggerService);
7
- analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?: {
8
- skipDependencyResolution?: boolean;
9
- }): Promise<Map<string, FileAnalysis>>;
6
+ constructor(logger?: ILogger);
7
+ analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?: AnalyzeProjectOpts): Promise<Map<string, FileAnalysis>>;
10
8
  clearCache(projectRoot?: string): void;
11
9
  analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
12
10
  extractImportEdges(filePath: string, serviceName: string, projectRoot?: string): Promise<DepEdge[]>;
@@ -54,9 +54,10 @@ class AstParserService {
54
54
  async analyzeProject(filePaths, serviceName, projectRoot = process.cwd(), opts = {}) {
55
55
  const t0 = Date.now();
56
56
  const skipDependencyResolution = opts.skipDependencyResolution ?? false;
57
- this.logger.debug('AstParser: analyzeProject start', { serviceName, projectRoot, files: filePaths.length, skipDependencyResolution });
57
+ const rootTsConfigPath = opts.rootTsConfigPath;
58
+ this.logger.debug('AstParser: analyzeProject start', { serviceName, projectRoot, files: filePaths.length, skipDependencyResolution, rootTsConfigPath });
58
59
  const analysisMap = new Map();
59
- const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution);
60
+ const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution, rootTsConfigPath);
60
61
  for (const filePath of filePaths) {
61
62
  if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
62
63
  analysisMap.set(filePath, { edges: [], symbols: [], calls: [] });
@@ -135,22 +136,37 @@ class AstParserService {
135
136
  return edges;
136
137
  }
137
138
  // ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
138
- createMorphProject(projectRoot, skipFileDependencyResolution = false) {
139
+ createMorphProject(projectRoot, skipFileDependencyResolution = false, rootTsConfigPath) {
139
140
  const t0 = Date.now();
140
- const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
141
- const hasTsconfig = fs.existsSync(tsconfigPath);
142
- this.logger.debug('AstParser: tsconfig lookup', { projectRoot, found: hasTsconfig, skipFileDependencyResolution });
141
+ // Determine which tsconfig to use:
142
+ // 1. Project-local tsconfig.json
143
+ // 2. Root tsconfig (e.g. tsconfig.base.json for Nx monorepos)
144
+ // 3. Fallback options
145
+ const localTsconfigPath = path.join(projectRoot, 'tsconfig.json');
146
+ const hasLocalTsconfig = fs.existsSync(localTsconfigPath);
147
+ const hasRootTsconfig = rootTsConfigPath ? fs.existsSync(rootTsConfigPath) : false;
148
+ // For monorepos: prefer the root tsconfig.base.json which contains
149
+ // workspace-wide path aliases (e.g. @scope/* → libs/*/src)
150
+ const effectiveTsconfig = hasRootTsconfig ? rootTsConfigPath : (hasLocalTsconfig ? localTsconfigPath : null);
151
+ this.logger.debug('AstParser: tsconfig lookup', {
152
+ projectRoot,
153
+ localFound: hasLocalTsconfig,
154
+ rootTsConfigPath: rootTsConfigPath ?? null,
155
+ rootFound: hasRootTsconfig,
156
+ effective: effectiveTsconfig,
157
+ skipFileDependencyResolution,
158
+ });
143
159
  let project;
144
160
  try {
145
- const opts = hasTsconfig
146
- ? { tsConfigFilePath: tsconfigPath, skipAddingFilesFromTsConfig: true, skipFileDependencyResolution }
161
+ const opts = effectiveTsconfig
162
+ ? { tsConfigFilePath: effectiveTsconfig, skipAddingFilesFromTsConfig: true, skipFileDependencyResolution }
147
163
  : { ...FALLBACK_OPTS, skipFileDependencyResolution };
148
164
  project = new ts_morph_1.Project(opts);
149
- if (hasTsconfig) {
165
+ if (effectiveTsconfig) {
150
166
  const co = project.getCompilerOptions();
151
167
  const pathsMap = co.paths ?? {};
152
168
  const pathCount = Object.keys(pathsMap).length;
153
- this.logger.debug('AstParser: tsconfig loaded', { pathCount, ms: Date.now() - t0 });
169
+ this.logger.debug('AstParser: tsconfig loaded', { tsconfig: effectiveTsconfig, pathCount, ms: Date.now() - t0 });
154
170
  if (pathCount > 0) {
155
171
  this.logger.debug('AstParser: path aliases', { aliases: Object.keys(pathsMap) });
156
172
  }
@@ -161,7 +177,7 @@ class AstParserService {
161
177
  }
162
178
  catch (err) {
163
179
  this.logger.warn('AstParser: tsconfig load failed, using fallback', {
164
- tsconfigPath,
180
+ tsconfigPath: effectiveTsconfig ?? localTsconfigPath,
165
181
  reason: err.message,
166
182
  ms: Date.now() - t0,
167
183
  });
@@ -367,7 +383,7 @@ class AstParserService {
367
383
  const sym = expr.getNameNode().getSymbol();
368
384
  const decl = sym?.getDeclarations()?.[0];
369
385
  const declPath = decl?.getSourceFile().getFilePath();
370
- if (declPath && !declPath.includes('/node_modules/') && declPath !== filePath) {
386
+ if (declPath && !declPath.includes('/node_modules/')) {
371
387
  calleeFile = declPath;
372
388
  calleeSymbol = calleeName;
373
389
  }
@@ -383,7 +399,7 @@ class AstParserService {
383
399
  : sym;
384
400
  const decl = resolved.getDeclarations()?.[0];
385
401
  const declPath = decl?.getSourceFile().getFilePath();
386
- if (declPath && !declPath.includes('/node_modules/') && declPath !== filePath) {
402
+ if (declPath && !declPath.includes('/node_modules/')) {
387
403
  calleeFile = declPath;
388
404
  calleeSymbol = calleeName;
389
405
  }
@@ -1,11 +1,12 @@
1
1
  import type { NodePgDatabase, NodePgTransaction } from 'drizzle-orm/node-postgres';
2
2
  import type { ExtractTablesWithRelations } from 'drizzle-orm/relations';
3
3
  import type { Config } from '../config';
4
+ import type { IDatabase } from '../interfaces/IDatabase';
4
5
  import * as schema from '../db/schema';
5
6
  type Schema = typeof schema;
6
7
  export type DrizzleDb = NodePgDatabase<Schema>;
7
8
  export type DrizzleTx = NodePgTransaction<Schema, ExtractTablesWithRelations<Schema>>;
8
- export declare class DatabaseService {
9
+ export declare class DatabaseService implements IDatabase {
9
10
  private config;
10
11
  private pool;
11
12
  private _db;
@@ -1,8 +1,8 @@
1
1
  import type { Config } from '../config';
2
2
  import type { IEmbedder } from '../interfaces/IEmbedder';
3
3
  import type { IAstParser } from '../interfaces/IAstParser';
4
- import type { DatabaseService } from './DatabaseService';
5
- import { LoggerService } from './LoggerService';
4
+ import type { IDatabase } from '../interfaces/IDatabase';
5
+ import type { ILogger } from '../interfaces/ILogger';
6
6
  export interface IndexStats {
7
7
  serviceName: string;
8
8
  indexed: number;
@@ -20,7 +20,7 @@ export declare class IndexerService {
20
20
  private logger;
21
21
  private readonly jsSplitter;
22
22
  private readonly schemaSplitter;
23
- constructor(db: DatabaseService, embedder: IEmbedder, ast: IAstParser, config: Config, logger?: LoggerService);
23
+ constructor(db: IDatabase, embedder: IEmbedder, ast: IAstParser, config: Config, logger?: ILogger);
24
24
  backfillSymbols(signal?: AbortSignal): Promise<{
25
25
  total: number;
26
26
  processed: number;
@@ -33,12 +33,10 @@ export declare class IndexerService {
33
33
  skipped: number;
34
34
  failed: number;
35
35
  }>;
36
- discoverProjects(rootDir: string): {
37
- name: string;
38
- path: string;
39
- }[];
40
36
  private writeDepEdges;
41
- scanAndIndex(dir: string, serviceName: string, force?: boolean, signal?: AbortSignal, preCollectedFiles?: string[]): Promise<IndexStats>;
37
+ scanAndIndex(dir: string, serviceName: string, force?: boolean, signal?: AbortSignal, preCollectedFiles?: string[], opts?: {
38
+ rootTsConfigPath?: string;
39
+ }): Promise<IndexStats>;
42
40
  private getSplitter;
43
41
  private static inferRole;
44
42
  private getEnrichedPrompt;
@@ -159,12 +159,6 @@ class IndexerService {
159
159
  this.logger.debug('IndexerService: backfillCalls done', { total: rows.length, processed, skipped, failed, ms: Date.now() - t0 });
160
160
  return { total: rows.length, processed, skipped, failed };
161
161
  }
162
- discoverProjects(rootDir) {
163
- return fs.readdirSync(rootDir, { withFileTypes: true })
164
- .filter(d => d.isDirectory() && !file_utils_1.IGNORE_DIRS.has(d.name))
165
- .map(d => ({ name: d.name, path: path.join(rootDir, d.name) }))
166
- .filter(p => fs.existsSync(path.join(p.path, 'package.json')));
167
- }
168
162
  async writeDepEdges(serviceName, relFilePath, edges, tx) {
169
163
  const run = async (t) => {
170
164
  await t.delete(db_1.codeDependency).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.codeDependency.project, serviceName), (0, drizzle_orm_1.eq)(db_1.codeDependency.fromFile, relFilePath)));
@@ -177,7 +171,7 @@ class IndexerService {
177
171
  else
178
172
  await this.db.transaction(run);
179
173
  }
180
- async scanAndIndex(dir, serviceName, force = false, signal, preCollectedFiles) {
174
+ async scanAndIndex(dir, serviceName, force = false, signal, preCollectedFiles, opts) {
181
175
  const startTime = Date.now();
182
176
  this.logger.debug('IndexerService: scanAndIndex start', { serviceName, dir, force });
183
177
  if (force) {
@@ -237,7 +231,10 @@ class IndexerService {
237
231
  if (signal?.aborted)
238
232
  break;
239
233
  const batch = files.slice(batchStart, batchStart + this.config.indexer.astBatchSize);
240
- const analysisMap = await this.ast.analyzeProject(batch, serviceName, dir, { skipDependencyResolution: isLargeProject });
234
+ const analysisMap = await this.ast.analyzeProject(batch, serviceName, dir, {
235
+ skipDependencyResolution: isLargeProject,
236
+ rootTsConfigPath: opts?.rootTsConfigPath,
237
+ });
241
238
  for (const filePath of batch) {
242
239
  if (signal?.aborted) {
243
240
  bar.stop();
@@ -1,6 +1,7 @@
1
1
  import pino from 'pino';
2
+ import type { ILogger } from '../interfaces/ILogger';
2
3
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
3
- export declare class LoggerService {
4
+ export declare class LoggerService implements ILogger {
4
5
  private readonly logger;
5
6
  constructor(logger: pino.Logger);
6
7
  static silent(): LoggerService;
@@ -1,14 +1,23 @@
1
1
  import type { Config } from '../config';
2
- import { SearcherService } from './SearcherService';
3
- import { LoggerService } from './LoggerService';
2
+ import type { ISearcher } from '../interfaces/ISearcher';
3
+ import type { ILogger } from '../interfaces/ILogger';
4
+ /**
5
+ * McpServer — thin orchestration layer that delegates tool handling
6
+ * to individual IMcpToolHandler implementations (Open/Closed Principle).
7
+ *
8
+ * To add a new tool: implement IMcpToolHandler and register it in createTools().
9
+ */
4
10
  export declare class McpServer {
5
11
  private searcher;
6
12
  private config;
7
13
  private logger;
8
- constructor(searcher: SearcherService, config: Config, logger?: LoggerService);
14
+ private readonly version;
15
+ private readonly tools;
16
+ constructor(searcher: ISearcher, config: Config, logger: ILogger);
9
17
  start(startupError?: Error): Promise<void>;
10
- private formatGraphContext;
11
- private formatChunk;
12
- private formatDependencies;
13
- private formatDependents;
18
+ /**
19
+ * Factory method: creates and registers all tool handlers.
20
+ * New tools are added here without modifying the dispatch logic above.
21
+ */
22
+ private createTools;
14
23
  }