maven-indexer-mcp 1.0.5 → 1.0.7

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/build/indexer.js CHANGED
@@ -15,7 +15,8 @@ export class Indexer {
15
15
  isIndexing = false;
16
16
  watcher = null;
17
17
  debounceTimer = null;
18
- constructor() { }
18
+ constructor() {
19
+ }
19
20
  static getInstance() {
20
21
  if (!Indexer.instance) {
21
22
  Indexer.instance = new Indexer();
@@ -44,7 +45,13 @@ export class Indexer {
44
45
  }
45
46
  console.error(`Starting file watcher on ${pathsToWatch.join(', ')}...`);
46
47
  this.watcher = chokidar.watch(pathsToWatch, {
47
- ignored: /(^|[\/\\])\../, // ignore dotfiles
48
+ ignored: (p) => {
49
+ // Don't ignore the root paths themselves
50
+ if (pathsToWatch.includes(p))
51
+ return false;
52
+ // Ignore dotfiles/dotdirs
53
+ return path.basename(p).startsWith('.');
54
+ },
48
55
  persistent: true,
49
56
  ignoreInitial: true,
50
57
  depth: 10 // Limit depth to avoid too much overhead? Standard maven repo depth is around 3-5
@@ -105,13 +112,13 @@ export class Indexer {
105
112
  let artifacts = [];
106
113
  if (repoPath && fsSync.existsSync(repoPath)) {
107
114
  console.error(`Scanning Maven repo: ${repoPath}`);
108
- const mavenArtifacts = await this.scanRepository(repoPath);
115
+ const mavenArtifacts = await this.scanRepository(repoPath, config.normalizedIncludedPackages);
109
116
  console.error(`Found ${mavenArtifacts.length} Maven artifacts.`);
110
117
  artifacts = artifacts.concat(mavenArtifacts);
111
118
  }
112
119
  if (gradleRepoPath && fsSync.existsSync(gradleRepoPath)) {
113
120
  console.error(`Scanning Gradle repo: ${gradleRepoPath}`);
114
- const gradleArtifacts = await this.scanGradleRepository(gradleRepoPath);
121
+ const gradleArtifacts = await this.scanGradleRepository(gradleRepoPath, config.normalizedIncludedPackages);
115
122
  console.error(`Found ${gradleArtifacts.length} Gradle artifacts.`);
116
123
  artifacts = artifacts.concat(gradleArtifacts);
117
124
  }
@@ -119,9 +126,10 @@ export class Indexer {
119
126
  // 2. Persist artifacts and determine what needs indexing
120
127
  // We use is_indexed = 0 for new artifacts.
121
128
  const insertArtifact = db.prepare(`
122
- INSERT OR IGNORE INTO artifacts (group_id, artifact_id, version, abspath, has_source, is_indexed)
123
- VALUES (@groupId, @artifactId, @version, @abspath, @hasSource, 0)
124
- `);
129
+ INSERT
130
+ OR IGNORE INTO artifacts (group_id, artifact_id, version, abspath, has_source, is_indexed)
131
+ VALUES (@groupId, @artifactId, @version, @abspath, @hasSource, 0)
132
+ `);
125
133
  // Use a transaction only for the batch insert of artifacts
126
134
  db.transaction(() => {
127
135
  for (const art of artifacts) {
@@ -144,10 +152,10 @@ export class Indexer {
144
152
  }
145
153
  // 3. Find artifacts that need indexing (is_indexed = 0)
146
154
  const artifactsToIndex = db.prepare(`
147
- SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
148
- FROM artifacts
149
- WHERE is_indexed = 0
150
- `).all();
155
+ SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
156
+ FROM artifacts
157
+ WHERE is_indexed = 0
158
+ `).all();
151
159
  console.error(`${artifactsToIndex.length} artifacts need indexing.`);
152
160
  // 4. Scan JARs for classes and update DB
153
161
  const CHUNK_SIZE = 50;
@@ -171,8 +179,11 @@ export class Indexer {
171
179
  }
172
180
  /**
173
181
  * Recursively scans a directory for Maven artifacts (POM files).
182
+ *
183
+ * @param repoRoot The root directory of the Maven repository.
184
+ * @param normalizedPatterns List of normalized package patterns to include.
174
185
  */
175
- async scanRepository(rootDir) {
186
+ async scanRepository(repoRoot, normalizedPatterns = []) {
176
187
  const results = [];
177
188
  const scanDir = async (dir) => {
178
189
  let entries;
@@ -188,7 +199,7 @@ export class Indexer {
188
199
  const artifactDir = path.dirname(dir);
189
200
  const artifactId = path.basename(artifactDir);
190
201
  const groupDir = path.dirname(artifactDir);
191
- const relGroupPath = path.relative(rootDir, groupDir);
202
+ const relGroupPath = path.relative(repoRoot, groupDir);
192
203
  const groupId = relGroupPath.split(path.sep).join('.');
193
204
  if (groupId && artifactId && version && !groupId.startsWith('..')) {
194
205
  const sourceJarPath = path.join(dir, `${artifactId}-${version}-sources.jar`);
@@ -213,14 +224,49 @@ export class Indexer {
213
224
  }
214
225
  }
215
226
  };
216
- await scanDir(rootDir);
227
+ const startDirs = this.getMavenStartDirs(repoRoot, normalizedPatterns);
228
+ console.error(`Scanning Maven directories: ${startDirs.join(', ')}`);
229
+ for (const startDir of startDirs) {
230
+ await scanDir(startDir);
231
+ }
217
232
  return results;
218
233
  }
234
+ /**
235
+ * Calculates the starting directories for Maven scanning based on included packages.
236
+ *
237
+ * @param repoRoot The root of the Maven repository.
238
+ * @param normalizedPatterns The list of normalized included packages.
239
+ */
240
+ getMavenStartDirs(repoRoot, normalizedPatterns) {
241
+ if (normalizedPatterns.length === 0) {
242
+ return [repoRoot];
243
+ }
244
+ return normalizedPatterns.map(p => path.join(repoRoot, p.split('.').join(path.sep)));
245
+ }
246
+ /**
247
+ * Checks if a group ID is included in the normalized patterns.
248
+ *
249
+ * @param groupId The group ID (e.g., "com.google.guava").
250
+ * @param normalizedPatterns The list of normalized patterns.
251
+ */
252
+ isGroupIncluded(groupId, normalizedPatterns) {
253
+ if (!normalizedPatterns || normalizedPatterns.length === 0)
254
+ return true;
255
+ for (const pattern of normalizedPatterns) {
256
+ if (groupId === pattern || groupId.startsWith(pattern + '.')) {
257
+ return true;
258
+ }
259
+ }
260
+ return false;
261
+ }
219
262
  /**
220
263
  * Scans a Gradle cache directory for artifacts.
221
264
  * Structure: group/artifact/version/hash/file
265
+ *
266
+ * @param rootDir The root directory of the Gradle cache (e.g., ~/.gradle/caches/modules-2/files-2.1).
267
+ * @param normalizedPatterns List of normalized package patterns to include.
222
268
  */
223
- async scanGradleRepository(rootDir) {
269
+ async scanGradleRepository(rootDir, normalizedPatterns = []) {
224
270
  const results = [];
225
271
  // Helper to read directory safely
226
272
  const readDirSafe = async (p) => {
@@ -236,6 +282,9 @@ export class Indexer {
236
282
  if (!groupEntry.isDirectory())
237
283
  continue;
238
284
  const groupId = groupEntry.name;
285
+ if (!this.isGroupIncluded(groupId, normalizedPatterns)) {
286
+ continue;
287
+ }
239
288
  const groupPath = path.join(rootDir, groupId);
240
289
  const artifactDirs = await readDirSafe(groupPath);
241
290
  for (const artifactEntry of artifactDirs) {
@@ -302,7 +351,7 @@ export class Indexer {
302
351
  await fs.access(jarPath);
303
352
  }
304
353
  catch {
305
- // If jar missing, mark as indexed so we don't retry endlessly?
354
+ // If jar missing, mark as indexed so we don't retry endlessly?
306
355
  // Or maybe it's a pom-only artifact.
307
356
  db.prepare('UPDATE artifacts SET is_indexed = 1 WHERE id = ?').run(artifact.id);
308
357
  return;
@@ -333,13 +382,21 @@ export class Indexer {
333
382
  // Simple check to avoid module-info or invalid names
334
383
  if (!info.className.includes('$') && info.className.length > 0) {
335
384
  // Filter by includedPackages
336
- if (this.isPackageIncluded(info.className, config.includedPackages)) {
385
+ if (this.isPackageIncluded(info.className, config.normalizedIncludedPackages)) {
337
386
  classes.push(info.className);
338
387
  if (info.superClass && info.superClass !== 'java.lang.Object') {
339
- inheritance.push({ className: info.className, parent: info.superClass, type: 'extends' });
388
+ inheritance.push({
389
+ className: info.className,
390
+ parent: info.superClass,
391
+ type: 'extends'
392
+ });
340
393
  }
341
394
  for (const iface of info.interfaces) {
342
- inheritance.push({ className: info.className, parent: iface, type: 'implements' });
395
+ inheritance.push({
396
+ className: info.className,
397
+ parent: iface,
398
+ type: 'implements'
399
+ });
343
400
  }
344
401
  }
345
402
  }
@@ -360,13 +417,13 @@ export class Indexer {
360
417
  try {
361
418
  db.transaction(() => {
362
419
  const insertClass = db.prepare(`
363
- INSERT INTO classes_fts (artifact_id, class_name, simple_name)
364
- VALUES (?, ?, ?)
365
- `);
420
+ INSERT INTO classes_fts (artifact_id, class_name, simple_name)
421
+ VALUES (?, ?, ?)
422
+ `);
366
423
  const insertInheritance = db.prepare(`
367
- INSERT INTO inheritance (artifact_id, class_name, parent_class_name, type)
368
- VALUES (?, ?, ?, ?)
369
- `);
424
+ INSERT INTO inheritance (artifact_id, class_name, parent_class_name, type)
425
+ VALUES (?, ?, ?, ?)
426
+ `);
370
427
  for (const cls of classes) {
371
428
  const simpleName = cls.split('.').pop() || cls;
372
429
  insertClass.run(artifact.id, cls, simpleName);
@@ -391,23 +448,17 @@ export class Indexer {
391
448
  }
392
449
  /**
393
450
  * Checks if a class package is included in the configuration patterns.
451
+ *
452
+ * @param className The fully qualified class name.
453
+ * @param normalizedPatterns The list of normalized package patterns.
394
454
  */
395
- isPackageIncluded(className, patterns) {
396
- if (patterns.length === 0 || (patterns.length === 1 && patterns[0] === '*'))
455
+ isPackageIncluded(className, normalizedPatterns) {
456
+ if (!normalizedPatterns || normalizedPatterns.length === 0)
397
457
  return true;
398
- for (const pattern of patterns) {
399
- if (pattern === '*')
458
+ for (const pattern of normalizedPatterns) {
459
+ // Match exact package or subpackage
460
+ if (className === pattern || className.startsWith(pattern + '.')) {
400
461
  return true;
401
- if (pattern.endsWith('.*')) {
402
- const prefix = pattern.slice(0, -2); // "com.example"
403
- // Match exact package or subpackage
404
- if (className === prefix || className.startsWith(prefix + '.'))
405
- return true;
406
- }
407
- else {
408
- // Exact match
409
- if (className === pattern)
410
- return true;
411
462
  }
412
463
  }
413
464
  return false;
@@ -419,11 +470,11 @@ export class Indexer {
419
470
  // Artifact coordinates search
420
471
  const db = DB.getInstance();
421
472
  const rows = db.prepare(`
422
- SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
423
- FROM artifacts
424
- WHERE group_id LIKE ? OR artifact_id LIKE ?
425
- LIMIT 50
426
- `).all(`%${query}%`, `%${query}%`);
473
+ SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
474
+ FROM artifacts
475
+ WHERE group_id LIKE ?
476
+ OR artifact_id LIKE ? LIMIT 50
477
+ `).all(`%${query}%`, `%${query}%`);
427
478
  return rows;
428
479
  }
429
480
  /**
@@ -438,12 +489,19 @@ export class Indexer {
438
489
  // Regex search
439
490
  const regex = classNamePattern.substring(6);
440
491
  rows = db.prepare(`
441
- SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
442
- FROM classes_fts c
443
- JOIN artifacts a ON c.artifact_id = a.id
444
- WHERE c.class_name REGEXP ? OR c.simple_name REGEXP ?
492
+ SELECT c.class_name,
493
+ c.simple_name,
494
+ a.id,
495
+ a.group_id,
496
+ a.artifact_id,
497
+ a.version,
498
+ a.abspath,
499
+ a.has_source
500
+ FROM classes_fts c
501
+ JOIN artifacts a ON c.artifact_id = a.id
502
+ WHERE c.class_name REGEXP ? OR c.simple_name REGEXP ?
445
503
  LIMIT 100
446
- `).all(regex, regex);
504
+ `).all(regex, regex);
447
505
  }
448
506
  else if (classNamePattern.includes('*') || classNamePattern.includes('?')) {
449
507
  // Glob-style search (using LIKE for standard wildcards)
@@ -451,12 +509,19 @@ export class Indexer {
451
509
  // But standard glob is * and ?
452
510
  const likePattern = classNamePattern.replace(/\*/g, '%').replace(/\?/g, '_');
453
511
  rows = db.prepare(`
454
- SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
455
- FROM classes_fts c
456
- JOIN artifacts a ON c.artifact_id = a.id
457
- WHERE c.class_name LIKE ? OR c.simple_name LIKE ?
458
- LIMIT 100
459
- `).all(likePattern, likePattern);
512
+ SELECT c.class_name,
513
+ c.simple_name,
514
+ a.id,
515
+ a.group_id,
516
+ a.artifact_id,
517
+ a.version,
518
+ a.abspath,
519
+ a.has_source
520
+ FROM classes_fts c
521
+ JOIN artifacts a ON c.artifact_id = a.id
522
+ WHERE c.class_name LIKE ?
523
+ OR c.simple_name LIKE ? LIMIT 100
524
+ `).all(likePattern, likePattern);
460
525
  }
461
526
  else {
462
527
  // Use FTS for smart matching
@@ -468,13 +533,19 @@ export class Indexer {
468
533
  ? `"${escapedPattern}"* OR ${safeQuery}`
469
534
  : `"${escapedPattern}"*`;
470
535
  rows = db.prepare(`
471
- SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
472
- FROM classes_fts c
473
- JOIN artifacts a ON c.artifact_id = a.id
474
- WHERE c.classes_fts MATCH ?
475
- ORDER BY rank
476
- LIMIT 100
477
- `).all(query);
536
+ SELECT c.class_name,
537
+ c.simple_name,
538
+ a.id,
539
+ a.group_id,
540
+ a.artifact_id,
541
+ a.version,
542
+ a.abspath,
543
+ a.has_source
544
+ FROM classes_fts c
545
+ JOIN artifacts a ON c.artifact_id = a.id
546
+ WHERE c.classes_fts MATCH ?
547
+ ORDER BY rank LIMIT 100
548
+ `).all(query);
478
549
  }
479
550
  // Group by class name
480
551
  const resultMap = new Map();
@@ -516,16 +587,17 @@ export class Indexer {
516
587
  }
517
588
  // Recursive search for all implementations/subclasses
518
589
  const rows = db.prepare(`
519
- WITH RECURSIVE hierarchy(class_name, artifact_id) AS (
520
- SELECT class_name, artifact_id FROM inheritance WHERE parent_class_name = ?
521
- UNION
522
- SELECT i.class_name, i.artifact_id FROM inheritance i JOIN hierarchy h ON i.parent_class_name = h.class_name
523
- )
524
- SELECT DISTINCT h.class_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
525
- FROM hierarchy h
526
- JOIN artifacts a ON h.artifact_id = a.id
527
- LIMIT 100
528
- `).all(className);
590
+ WITH RECURSIVE hierarchy(class_name, artifact_id) AS (SELECT class_name, artifact_id
591
+ FROM inheritance
592
+ WHERE parent_class_name = ?
593
+ UNION
594
+ SELECT i.class_name, i.artifact_id
595
+ FROM inheritance i
596
+ JOIN hierarchy h ON i.parent_class_name = h.class_name)
597
+ SELECT DISTINCT h.class_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
598
+ FROM hierarchy h
599
+ JOIN artifacts a ON h.artifact_id = a.id LIMIT 100
600
+ `).all(className);
529
601
  console.error(`Searching implementations for ${className}: found ${rows.length} rows.`);
530
602
  if (rows.length === 0) {
531
603
  // Fallback: Try searching without recursion to see if direct children exist
@@ -563,9 +635,10 @@ export class Indexer {
563
635
  getArtifactById(id) {
564
636
  const db = DB.getInstance();
565
637
  const row = db.prepare(`
566
- SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
567
- FROM artifacts WHERE id = ?
568
- `).get(id);
638
+ SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
639
+ FROM artifacts
640
+ WHERE id = ?
641
+ `).get(id);
569
642
  if (row) {
570
643
  return {
571
644
  id: row.id,
@@ -584,10 +657,12 @@ export class Indexer {
584
657
  getArtifactByCoordinate(groupId, artifactId, version) {
585
658
  const db = DB.getInstance();
586
659
  const row = db.prepare(`
587
- SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
588
- FROM artifacts
589
- WHERE group_id = ? AND artifact_id = ? AND version = ?
590
- `).get(groupId, artifactId, version);
660
+ SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
661
+ FROM artifacts
662
+ WHERE group_id = ?
663
+ AND artifact_id = ?
664
+ AND version = ?
665
+ `).get(groupId, artifactId, version);
591
666
  if (row) {
592
667
  return {
593
668
  id: row.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maven-indexer-mcp",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "MCP server for indexing local Maven repository",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -46,10 +46,12 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@modelcontextprotocol/sdk": "^1.24.3",
49
+ "@types/semver": "^7.7.1",
49
50
  "@types/xml2js": "^0.4.14",
50
51
  "@types/yauzl": "^2.10.3",
51
52
  "better-sqlite3": "^12.5.0",
52
53
  "chokidar": "^5.0.0",
54
+ "semver": "^7.7.3",
53
55
  "xml2js": "^0.6.2",
54
56
  "yauzl": "^3.2.0",
55
57
  "zod": "^4.1.13"
@@ -1,20 +0,0 @@
1
- import { Indexer } from "./indexer.js";
2
- import { DB } from "./db/index.js";
3
- async function main() {
4
- const db = DB.getInstance();
5
- const indexer = Indexer.getInstance();
6
- // We assume indexing is done since we just ran it.
7
- console.log("Searching for 'Kotlin'...");
8
- const kotlinResults = db.prepare("SELECT class_name FROM classes_fts WHERE class_name LIKE '%Kotlin%' LIMIT 10").all();
9
- console.log("First 10 Kotlin classes:", kotlinResults);
10
- console.log("Searching for 'AnAction'...");
11
- const anActionResults = indexer.searchClass("AnAction");
12
- console.log(`Found ${anActionResults.length} results for 'AnAction'.`);
13
- if (anActionResults.length > 0) {
14
- console.log(anActionResults[0]);
15
- }
16
- console.log("Searching for 'StringUtils'...");
17
- const stringUtilsResults = indexer.searchClass("StringUtils");
18
- console.log(`Found ${stringUtilsResults.length} results for 'StringUtils'.`);
19
- }
20
- main().catch(console.error);