codesearch-mcp 0.0.17-alpha → 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.
- package/CHANGELOG.md +25 -0
- package/README.md +30 -26
- package/dist/app.js +34 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +22 -7
- package/dist/src/commands/McpCommand.js +4 -3
- package/dist/src/commands/WebCommand.d.ts +8 -0
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +43 -0
- package/dist/src/config-store.js +92 -27
- package/dist/src/config.js +16 -17
- package/dist/src/constants.d.ts +2 -1
- package/dist/src/constants.js +6 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +6 -3
- package/dist/src/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +15 -1
- package/dist/src/interfaces/IAstParser.d.ts +6 -3
- package/dist/src/interfaces/IDatabase.d.ts +13 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +1 -0
- package/dist/src/interfaces/ILogger.d.ts +10 -0
- package/dist/src/interfaces/ILogger.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
- package/dist/src/interfaces/IMcpToolHandler.js +2 -0
- package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
- package/dist/src/interfaces/IMonorepoResolver.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
- package/dist/src/interfaces/IProjectDiscovery.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +36 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +7 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstParserService.d.ts +4 -6
- package/dist/src/services/AstParserService.js +29 -13
- package/dist/src/services/DatabaseService.d.ts +2 -1
- package/dist/src/services/IndexerService.d.ts +6 -8
- package/dist/src/services/IndexerService.js +5 -8
- package/dist/src/services/LoggerService.d.ts +2 -1
- package/dist/src/services/McpServer.d.ts +16 -7
- package/dist/src/services/McpServer.js +54 -367
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
- package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
- package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
- package/dist/src/services/NxMonorepoResolver.js +308 -0
- package/dist/src/services/OllamaEmbedder.d.ts +5 -2
- package/dist/src/services/OllamaEmbedder.js +9 -2
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/services/ProjectDiscoveryService.js +52 -0
- package/dist/src/services/SearcherService.d.ts +39 -6
- package/dist/src/services/SearcherService.js +151 -40
- package/dist/src/services/WebServerService.d.ts +72 -0
- package/dist/src/services/WebServerService.js +247 -0
- package/dist/src/setup.js +7 -7
- package/dist/src/tools/FindSymbolTool.d.ts +8 -0
- package/dist/src/tools/FindSymbolTool.js +45 -0
- package/dist/src/tools/GetCallersTool.d.ts +8 -0
- package/dist/src/tools/GetCallersTool.js +42 -0
- package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
- package/dist/src/tools/GetDependenciesTool.js +38 -0
- package/dist/src/tools/GetDependentsTool.d.ts +8 -0
- package/dist/src/tools/GetDependentsTool.js +57 -0
- package/dist/src/tools/ListFilesTool.d.ts +8 -0
- package/dist/src/tools/ListFilesTool.js +39 -0
- package/dist/src/tools/ListProjectsTool.d.ts +8 -0
- package/dist/src/tools/ListProjectsTool.js +44 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
- package/dist/src/tools/ReadFileRangeTool.js +44 -0
- package/dist/src/tools/ReadFileTool.d.ts +8 -0
- package/dist/src/tools/ReadFileTool.js +42 -0
- package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
- package/dist/src/tools/SearchCodebaseTool.js +81 -0
- package/dist/src/tools/format-helpers.d.ts +8 -0
- package/dist/src/tools/format-helpers.js +57 -0
- package/dist/src/tools/index.d.ts +9 -0
- package/dist/src/tools/index.js +21 -0
- package/dist/src/web/dashboard.d.ts +9 -0
- package/dist/src/web/dashboard.js +337 -0
- package/docs/web-dashboard.md +187 -0
- 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,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';
|
|
@@ -7,7 +7,7 @@ export declare class ProjectSelector {
|
|
|
7
7
|
private included;
|
|
8
8
|
private skipped;
|
|
9
9
|
constructor(workspaceDir: string);
|
|
10
|
-
select(allProjects:
|
|
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 {
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 =
|
|
146
|
-
? { tsConfigFilePath:
|
|
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 (
|
|
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/')
|
|
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/')
|
|
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 {
|
|
5
|
-
import {
|
|
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:
|
|
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[]
|
|
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, {
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
14
|
+
private readonly version;
|
|
15
|
+
private readonly tools;
|
|
16
|
+
constructor(searcher: ISearcher, config: Config, logger: ILogger);
|
|
9
17
|
start(startupError?: Error): Promise<void>;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
}
|