maven-indexer-mcp 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -5,6 +5,7 @@ A Model Context Protocol (MCP) server that indexes your local Maven repository (
5
5
  ## Features
6
6
 
7
7
  * **Semantic Class Search**: Search for classes by name (e.g., `StringUtils`) or purpose (e.g., `JsonToXml`).
8
+ * **Inheritance Search**: Find all implementations of an interface or subclasses of a class.
8
9
  * **On-Demand Analysis**: Extracts method signatures (`javap`) and Javadocs directly from JARs without extracting the entire archive.
9
10
  * **Source Code Retrieval**: Provides full source code if `-sources.jar` is available.
10
11
  * **Real-time Monitoring**: Watches the Maven repository for changes (e.g., new `mvn install`) and automatically updates the index.
@@ -90,6 +91,10 @@ If you prefer to run from source:
90
91
  * Input: `className`, `artifactId`, `type` ("signatures", "docs", "source")
91
92
  * Output: Method signatures, Javadocs, or full source code.
92
93
  * **`search_artifacts`**: Search for artifacts by coordinate (groupId, artifactId).
94
+ * **`search_implementations`**: Search for classes that implement a specific interface or extend a specific class.
95
+ * Input: `className` (e.g. "java.util.List")
96
+ * Output: List of implementation/subclass names and their artifacts.
97
+ * **`refresh_index`**: Trigger a re-scan of the Maven repository.
93
98
 
94
99
  ## Development
95
100
 
@@ -0,0 +1,120 @@
1
+ export class ClassParser {
2
+ buffer;
3
+ offset = 0;
4
+ constantPool = [];
5
+ constructor(buffer) {
6
+ this.buffer = buffer;
7
+ }
8
+ static parse(buffer) {
9
+ const parser = new ClassParser(buffer);
10
+ return parser.parse();
11
+ }
12
+ parse() {
13
+ if (this.buffer.length < 10) {
14
+ throw new Error("Invalid class file: too short");
15
+ }
16
+ const magic = this.readU4();
17
+ if (magic !== 0xCAFEBABE) {
18
+ throw new Error("Invalid magic number");
19
+ }
20
+ this.readU2(); // minor
21
+ this.readU2(); // major
22
+ const cpCount = this.readU2();
23
+ this.constantPool = new Array(cpCount);
24
+ // Constant Pool is 1-indexed (1 to count-1)
25
+ for (let i = 1; i < cpCount; i++) {
26
+ const tag = this.readU1();
27
+ switch (tag) {
28
+ case 1: // UTF8
29
+ const len = this.readU2();
30
+ const str = this.buffer.toString('utf-8', this.offset, this.offset + len);
31
+ this.offset += len;
32
+ this.constantPool[i] = { tag, value: str };
33
+ break;
34
+ case 3: // Integer
35
+ case 4: // Float
36
+ this.offset += 4;
37
+ break;
38
+ case 5: // Long
39
+ case 6: // Double
40
+ this.offset += 8;
41
+ i++; // Takes two slots
42
+ break;
43
+ case 7: // Class
44
+ const nameIndex = this.readU2();
45
+ this.constantPool[i] = { tag, nameIndex };
46
+ break;
47
+ case 8: // String
48
+ this.offset += 2;
49
+ break;
50
+ case 9: // Fieldref
51
+ case 10: // Methodref
52
+ case 11: // InterfaceMethodref
53
+ this.offset += 4;
54
+ break;
55
+ case 12: // NameAndType
56
+ this.offset += 4;
57
+ break;
58
+ case 15: // MethodHandle
59
+ this.offset += 3;
60
+ break;
61
+ case 16: // MethodType
62
+ this.offset += 2;
63
+ break;
64
+ case 17: // Dynamic
65
+ case 18: // InvokeDynamic
66
+ this.offset += 4;
67
+ break;
68
+ case 19: // Module
69
+ case 20: // Package
70
+ this.offset += 2;
71
+ break;
72
+ default:
73
+ throw new Error(`Unknown constant pool tag: ${tag} at offset ${this.offset - 1}`);
74
+ }
75
+ }
76
+ this.readU2(); // Access flags
77
+ const thisClassIndex = this.readU2();
78
+ const superClassIndex = this.readU2();
79
+ const className = this.resolveClass(thisClassIndex);
80
+ const superClass = superClassIndex === 0 ? undefined : this.resolveClass(superClassIndex);
81
+ const interfacesCount = this.readU2();
82
+ const interfaces = [];
83
+ for (let i = 0; i < interfacesCount; i++) {
84
+ const interfaceIndex = this.readU2();
85
+ interfaces.push(this.resolveClass(interfaceIndex));
86
+ }
87
+ return {
88
+ className: className.replace(/\//g, '.'),
89
+ superClass: superClass ? superClass.replace(/\//g, '.') : undefined,
90
+ interfaces: interfaces.map(i => i.replace(/\//g, '.'))
91
+ };
92
+ }
93
+ resolveClass(index) {
94
+ const entry = this.constantPool[index];
95
+ if (!entry || entry.tag !== 7) {
96
+ // Fallback or error?
97
+ return "Unknown";
98
+ }
99
+ const nameEntry = this.constantPool[entry.nameIndex];
100
+ if (!nameEntry || nameEntry.tag !== 1) {
101
+ return "Unknown";
102
+ }
103
+ return nameEntry.value;
104
+ }
105
+ readU1() {
106
+ const val = this.buffer.readUInt8(this.offset);
107
+ this.offset += 1;
108
+ return val;
109
+ }
110
+ readU2() {
111
+ const val = this.buffer.readUInt16BE(this.offset);
112
+ this.offset += 2;
113
+ return val;
114
+ }
115
+ readU4() {
116
+ const val = this.buffer.readUInt32BE(this.offset);
117
+ this.offset += 4;
118
+ return val;
119
+ }
120
+ }
package/build/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import xml2js from 'xml2js';
5
+ import { fileURLToPath } from 'url';
5
6
  export class Config {
6
7
  static instance;
7
8
  localRepository = "";
@@ -69,7 +70,9 @@ export class Config {
69
70
  }
70
71
  else {
71
72
  // Fallback to default bundled lib
72
- this.cfrPath = path.resolve(process.cwd(), 'lib/cfr-0.152.jar');
73
+ const __filename = fileURLToPath(import.meta.url);
74
+ const __dirname = path.dirname(__filename);
75
+ this.cfrPath = path.resolve(__dirname, '../lib/cfr-0.152.jar');
73
76
  }
74
77
  // Log to stderr so it doesn't interfere with MCP protocol on stdout
75
78
  console.error(`Using local repository: ${this.localRepository}`);
package/build/db/index.js CHANGED
@@ -17,6 +17,17 @@ export class DB {
17
17
  return DB.instance;
18
18
  }
19
19
  initSchema() {
20
+ // Register REGEXP function
21
+ this.db.function('regexp', { deterministic: true }, (regex, text) => {
22
+ if (!regex || !text)
23
+ return 0;
24
+ try {
25
+ return new RegExp(regex).test(text) ? 1 : 0;
26
+ }
27
+ catch (e) {
28
+ return 0;
29
+ }
30
+ });
20
31
  this.db.exec(`
21
32
  CREATE TABLE IF NOT EXISTS artifacts (
22
33
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -35,6 +46,16 @@ export class DB {
35
46
  simple_name, -- Just the class name
36
47
  tokenize="trigram"
37
48
  );
49
+
50
+ CREATE TABLE IF NOT EXISTS inheritance (
51
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
52
+ artifact_id INTEGER NOT NULL,
53
+ class_name TEXT NOT NULL,
54
+ parent_class_name TEXT NOT NULL,
55
+ type TEXT NOT NULL
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_inheritance_parent ON inheritance(parent_class_name);
38
59
 
39
60
  -- Cleanup old table if exists
40
61
  DROP TABLE IF EXISTS indexed_artifacts;
package/build/index.js CHANGED
@@ -21,7 +21,7 @@ indexer.index().then(() => {
21
21
  return indexer.startWatch();
22
22
  }).catch(err => console.error("Initial indexing failed:", err));
23
23
  server.registerTool("search_artifacts", {
24
- description: "Search for artifacts in the local Maven repository",
24
+ description: "Search for Maven artifacts (libraries) in the local repository by coordinate (groupId, artifactId) or keyword. Use this to find available versions of a library.",
25
25
  inputSchema: z.object({
26
26
  query: z.string().describe("Search query (groupId, artifactId, or keyword)"),
27
27
  }),
@@ -42,7 +42,7 @@ server.registerTool("search_artifacts", {
42
42
  };
43
43
  });
44
44
  server.registerTool("search_classes", {
45
- description: "Search for Java classes. Can be used to find classes by name or to find classes for a specific purpose (by searching keywords in class names).",
45
+ description: "Search for Java classes in the local Maven repository. Use this to find classes from external dependencies/libraries when their source code is not available in the current workspace. You can search by class name (e.g. 'StringUtils') or keywords (e.g. 'Json').",
46
46
  inputSchema: z.object({
47
47
  className: z.string().describe("Fully qualified class name, partial name, or keywords describing the class purpose (e.g. 'JsonToXml')."),
48
48
  }),
@@ -60,8 +60,26 @@ server.registerTool("search_classes", {
60
60
  content: [{ type: "text", text }]
61
61
  };
62
62
  });
63
+ server.registerTool("search_implementations", {
64
+ description: "Search for classes that implement a specific interface or extend a specific class. This is useful for finding implementations of SPIs or base classes.",
65
+ inputSchema: z.object({
66
+ className: z.string().describe("Fully qualified class name of the interface or base class (e.g. 'java.util.List')"),
67
+ }),
68
+ }, async ({ className }) => {
69
+ const matches = indexer.searchImplementations(className);
70
+ const text = matches.length > 0
71
+ ? matches.map(m => {
72
+ const artifacts = m.artifacts.slice(0, 5).map(a => `[ID: ${a.id}] ${a.groupId}:${a.artifactId}:${a.version}`).join("\n ");
73
+ const more = m.artifacts.length > 5 ? `\n ... (${m.artifacts.length - 5} more versions)` : '';
74
+ return `Implementation: ${m.className}\n ${artifacts}${more}`;
75
+ }).join("\n\n")
76
+ : `No implementations found for ${className}. Ensure the index is up to date and the class name is correct.`;
77
+ return {
78
+ content: [{ type: "text", text }]
79
+ };
80
+ });
63
81
  server.registerTool("get_class_details", {
64
- description: "Get details about a specific class from an artifact, including method signatures and javadocs (if source is available).",
82
+ description: "Get details about a specific class from a Maven artifact. Use this to inspect external classes where source code is missing. It can provide method signatures (using javap), Javadocs, or even decompiled source code if the original source jar is unavailable.",
65
83
  inputSchema: z.object({
66
84
  className: z.string().describe("Fully qualified class name"),
67
85
  artifactId: z.number().describe("The internal ID of the artifact (returned by search_classes)"),
@@ -74,6 +92,7 @@ server.registerTool("get_class_details", {
74
92
  }
75
93
  let detail = null;
76
94
  let usedDecompilation = false;
95
+ let lastError = "";
77
96
  // 1. If requesting source/docs, try Source JAR first
78
97
  if (type === 'source' || type === 'docs') {
79
98
  if (artifact.hasSource) {
@@ -83,6 +102,7 @@ server.registerTool("get_class_details", {
83
102
  }
84
103
  catch (e) {
85
104
  // Ignore error and fallthrough to main jar (decompilation)
105
+ lastError = e.message;
86
106
  }
87
107
  }
88
108
  // If not found in source jar (or no source jar), try main jar (decompilation)
@@ -96,18 +116,26 @@ server.registerTool("get_class_details", {
96
116
  }
97
117
  }
98
118
  catch (e) {
99
- // Ignore
119
+ console.error(`Decompilation/MainJar access failed: ${e.message}`);
120
+ lastError = e.message;
100
121
  }
101
122
  }
102
123
  }
103
124
  else {
104
125
  // Signatures -> Use Main JAR
105
126
  const mainJarPath = path.join(artifact.abspath, `${artifact.artifactId}-${artifact.version}.jar`);
106
- detail = await SourceParser.getClassDetail(mainJarPath, className, type);
127
+ try {
128
+ detail = await SourceParser.getClassDetail(mainJarPath, className, type);
129
+ }
130
+ catch (e) {
131
+ lastError = e.message;
132
+ }
107
133
  }
108
134
  try {
109
135
  if (!detail) {
110
- return { content: [{ type: "text", text: `Class ${className} not found in artifact ${artifact.artifactId}.` }] };
136
+ const debugInfo = `Artifact path: ${artifact.abspath}, hasSource: ${artifact.hasSource}`;
137
+ const errorMsg = lastError ? `\nLast error: ${lastError}` : "";
138
+ return { content: [{ type: "text", text: `Class ${className} not found in artifact ${artifact.artifactId}. \nDebug info: ${debugInfo}${errorMsg}` }] };
111
139
  }
112
140
  let resultText = `Class: ${detail.className}\n\n`;
113
141
  if (usedDecompilation) {
@@ -131,12 +159,12 @@ server.registerTool("get_class_details", {
131
159
  }
132
160
  });
133
161
  server.registerTool("refresh_index", {
134
- description: "Trigger a re-scan of the Maven repository",
162
+ description: "Trigger a re-scan of the Maven repository. This will re-index all artifacts.",
135
163
  }, async () => {
136
164
  // Re-run index
137
- indexer.index().catch(console.error);
165
+ indexer.refresh().catch(console.error);
138
166
  return {
139
- content: [{ type: "text", text: "Index refresh started." }]
167
+ content: [{ type: "text", text: "Index refresh started. All artifacts will be re-indexed." }]
140
168
  };
141
169
  });
142
170
  const transport = new StdioServerTransport();
package/build/indexer.js CHANGED
@@ -5,6 +5,7 @@ import yauzl from 'yauzl';
5
5
  import chokidar from 'chokidar';
6
6
  import { Config } from './config.js';
7
7
  import { DB } from './db/index.js';
8
+ import { ClassParser } from './class_parser.js';
8
9
  /**
9
10
  * Singleton class responsible for indexing Maven artifacts.
10
11
  * It scans the local repository, watches for changes, and indexes Java classes.
@@ -60,6 +61,19 @@ export class Indexer {
60
61
  onChange();
61
62
  });
62
63
  }
64
+ /**
65
+ * Forces a full re-index of the repository.
66
+ */
67
+ async refresh() {
68
+ const db = DB.getInstance();
69
+ console.error("Refreshing index...");
70
+ db.transaction(() => {
71
+ db.prepare('UPDATE artifacts SET is_indexed = 0').run();
72
+ db.prepare('DELETE FROM classes_fts').run();
73
+ db.prepare('DELETE FROM inheritance').run();
74
+ });
75
+ return this.index();
76
+ }
63
77
  /**
64
78
  * Main indexing process.
65
79
  * 1. Scans the file system for Maven artifacts.
@@ -71,15 +85,14 @@ export class Indexer {
71
85
  return;
72
86
  this.isIndexing = true;
73
87
  console.error("Starting index...");
74
- const config = await Config.getInstance();
75
- const repoPath = config.localRepository;
76
- const db = DB.getInstance();
77
- if (!repoPath) {
78
- console.error("No local repository path found.");
79
- this.isIndexing = false;
80
- return;
81
- }
82
88
  try {
89
+ const config = await Config.getInstance();
90
+ const repoPath = config.localRepository;
91
+ const db = DB.getInstance();
92
+ if (!repoPath) {
93
+ console.error("No local repository path found.");
94
+ return;
95
+ }
83
96
  // 1. Scan for artifacts
84
97
  console.error("Scanning repository structure...");
85
98
  const artifacts = await this.scanRepository(repoPath);
@@ -99,6 +112,17 @@ export class Indexer {
99
112
  });
100
113
  }
101
114
  });
115
+ // Check if we need to backfill inheritance data (migration)
116
+ const inheritanceCount = db.prepare('SELECT COUNT(*) as count FROM inheritance').get();
117
+ const indexedArtifactsCount = db.prepare('SELECT COUNT(*) as count FROM artifacts WHERE is_indexed = 1').get();
118
+ if (inheritanceCount.count === 0 && indexedArtifactsCount.count > 0) {
119
+ console.error("Detected missing inheritance data. Forcing re-index of classes...");
120
+ db.transaction(() => {
121
+ db.prepare('UPDATE artifacts SET is_indexed = 0').run();
122
+ db.prepare('DELETE FROM classes_fts').run();
123
+ // inheritance is already empty
124
+ });
125
+ }
102
126
  // 3. Find artifacts that need indexing (is_indexed = 0)
103
127
  const artifactsToIndex = db.prepare(`
104
128
  SELECT id, group_id as groupId, artifact_id as artifactId, version, abspath, has_source as hasSource
@@ -198,20 +222,45 @@ export class Indexer {
198
222
  return;
199
223
  }
200
224
  const classes = [];
225
+ const inheritance = [];
201
226
  zipfile.readEntry();
202
227
  zipfile.on('entry', (entry) => {
203
228
  if (entry.fileName.endsWith('.class')) {
204
- // Convert path/to/MyClass.class -> path.to.MyClass
205
- const className = entry.fileName.slice(0, -6).replace(/\//g, '.');
206
- // Simple check to avoid module-info or invalid names
207
- if (!className.includes('$') && className.length > 0) {
208
- // Filter by includedPackages
209
- if (this.isPackageIncluded(className, config.includedPackages)) {
210
- classes.push(className);
229
+ zipfile.openReadStream(entry, (err, readStream) => {
230
+ if (err || !readStream) {
231
+ zipfile.readEntry();
232
+ return;
211
233
  }
212
- }
234
+ const chunks = [];
235
+ readStream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
236
+ readStream.on('end', () => {
237
+ const buffer = Buffer.concat(chunks);
238
+ try {
239
+ const info = ClassParser.parse(buffer);
240
+ // Simple check to avoid module-info or invalid names
241
+ if (!info.className.includes('$') && info.className.length > 0) {
242
+ // Filter by includedPackages
243
+ if (this.isPackageIncluded(info.className, config.includedPackages)) {
244
+ classes.push(info.className);
245
+ if (info.superClass && info.superClass !== 'java.lang.Object') {
246
+ inheritance.push({ className: info.className, parent: info.superClass, type: 'extends' });
247
+ }
248
+ for (const iface of info.interfaces) {
249
+ inheritance.push({ className: info.className, parent: iface, type: 'implements' });
250
+ }
251
+ }
252
+ }
253
+ }
254
+ catch (e) {
255
+ // console.error(`Failed to parse ${entry.fileName}`, e);
256
+ }
257
+ zipfile.readEntry();
258
+ });
259
+ });
260
+ }
261
+ else {
262
+ zipfile.readEntry();
213
263
  }
214
- zipfile.readEntry();
215
264
  });
216
265
  zipfile.on('end', () => {
217
266
  // Batch insert classes
@@ -220,11 +269,18 @@ export class Indexer {
220
269
  const insertClass = db.prepare(`
221
270
  INSERT INTO classes_fts (artifact_id, class_name, simple_name)
222
271
  VALUES (?, ?, ?)
272
+ `);
273
+ const insertInheritance = db.prepare(`
274
+ INSERT INTO inheritance (artifact_id, class_name, parent_class_name, type)
275
+ VALUES (?, ?, ?, ?)
223
276
  `);
224
277
  for (const cls of classes) {
225
278
  const simpleName = cls.split('.').pop() || cls;
226
279
  insertClass.run(artifact.id, cls, simpleName);
227
280
  }
281
+ for (const item of inheritance) {
282
+ insertInheritance.run(artifact.id, item.className, item.parent, item.type);
283
+ }
228
284
  // Mark as indexed
229
285
  db.prepare('UPDATE artifacts SET is_indexed = 1 WHERE id = ?').run(artifact.id);
230
286
  });
@@ -283,23 +339,50 @@ export class Indexer {
283
339
  */
284
340
  searchClass(classNamePattern) {
285
341
  const db = DB.getInstance();
286
- // Use FTS for smart matching
287
- // If pattern has no spaces, we assume it's a prefix or exact match query
288
- // "String" -> match "String" in simple_name or class_name
289
- const escapedPattern = classNamePattern.replace(/"/g, '""');
290
- const safeQuery = classNamePattern.replace(/[^a-zA-Z0-9]/g, ' ').trim();
291
- const query = safeQuery.length > 0
292
- ? `"${escapedPattern}"* OR ${safeQuery}`
293
- : `"${escapedPattern}"*`;
294
342
  try {
295
- const rows = db.prepare(`
296
- SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
297
- FROM classes_fts c
298
- JOIN artifacts a ON c.artifact_id = a.id
299
- WHERE c.classes_fts MATCH ?
300
- ORDER BY rank
301
- LIMIT 100
302
- `).all(query);
343
+ let rows = [];
344
+ if (classNamePattern.startsWith('regex:')) {
345
+ // Regex search
346
+ const regex = classNamePattern.substring(6);
347
+ rows = db.prepare(`
348
+ SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
349
+ FROM classes_fts c
350
+ JOIN artifacts a ON c.artifact_id = a.id
351
+ WHERE c.class_name REGEXP ? OR c.simple_name REGEXP ?
352
+ LIMIT 100
353
+ `).all(regex, regex);
354
+ }
355
+ else if (classNamePattern.includes('*') || classNamePattern.includes('?')) {
356
+ // Glob-style search (using LIKE for standard wildcards)
357
+ // Convert glob wildcards to SQL wildcards if needed, or just rely on user knowing %/_
358
+ // But standard glob is * and ?
359
+ const likePattern = classNamePattern.replace(/\*/g, '%').replace(/\?/g, '_');
360
+ rows = db.prepare(`
361
+ SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
362
+ FROM classes_fts c
363
+ JOIN artifacts a ON c.artifact_id = a.id
364
+ WHERE c.class_name LIKE ? OR c.simple_name LIKE ?
365
+ LIMIT 100
366
+ `).all(likePattern, likePattern);
367
+ }
368
+ else {
369
+ // Use FTS for smart matching
370
+ // If pattern has no spaces, we assume it's a prefix or exact match query
371
+ // "String" -> match "String" in simple_name or class_name
372
+ const escapedPattern = classNamePattern.replace(/"/g, '""');
373
+ const safeQuery = classNamePattern.replace(/[^a-zA-Z0-9]/g, ' ').trim();
374
+ const query = safeQuery.length > 0
375
+ ? `"${escapedPattern}"* OR ${safeQuery}`
376
+ : `"${escapedPattern}"*`;
377
+ rows = db.prepare(`
378
+ SELECT c.class_name, c.simple_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
379
+ FROM classes_fts c
380
+ JOIN artifacts a ON c.artifact_id = a.id
381
+ WHERE c.classes_fts MATCH ?
382
+ ORDER BY rank
383
+ LIMIT 100
384
+ `).all(query);
385
+ }
303
386
  // Group by class name
304
387
  const resultMap = new Map();
305
388
  for (const row of rows) {
@@ -326,6 +409,61 @@ export class Indexer {
326
409
  return [];
327
410
  }
328
411
  }
412
+ /**
413
+ * Searches for implementations/subclasses of a specific class/interface.
414
+ */
415
+ searchImplementations(className) {
416
+ const db = DB.getInstance();
417
+ try {
418
+ console.error(`Searching implementations for ${className}...`);
419
+ // Debug: Check if we have any inheritance data at all
420
+ const count = db.prepare("SELECT count(*) as c FROM inheritance").get();
421
+ if (count.c === 0) {
422
+ console.error("WARNING: Inheritance table is empty!");
423
+ }
424
+ // Recursive search for all implementations/subclasses
425
+ const rows = db.prepare(`
426
+ WITH RECURSIVE hierarchy(class_name, artifact_id) AS (
427
+ SELECT class_name, artifact_id FROM inheritance WHERE parent_class_name = ?
428
+ UNION
429
+ SELECT i.class_name, i.artifact_id FROM inheritance i JOIN hierarchy h ON i.parent_class_name = h.class_name
430
+ )
431
+ SELECT DISTINCT h.class_name, a.id, a.group_id, a.artifact_id, a.version, a.abspath, a.has_source
432
+ FROM hierarchy h
433
+ JOIN artifacts a ON h.artifact_id = a.id
434
+ LIMIT 100
435
+ `).all(className);
436
+ console.error(`Searching implementations for ${className}: found ${rows.length} rows.`);
437
+ if (rows.length === 0) {
438
+ // Fallback: Try searching without recursion to see if direct children exist
439
+ const direct = db.prepare('SELECT count(*) as c FROM inheritance WHERE parent_class_name = ?').get(className);
440
+ console.error(`Direct implementations check for ${className}: ${direct.c}`);
441
+ }
442
+ const resultMap = new Map();
443
+ for (const row of rows) {
444
+ const art = {
445
+ id: row.id,
446
+ groupId: row.group_id,
447
+ artifactId: row.artifact_id,
448
+ version: row.version,
449
+ abspath: row.abspath,
450
+ hasSource: Boolean(row.has_source)
451
+ };
452
+ if (!resultMap.has(row.class_name)) {
453
+ resultMap.set(row.class_name, []);
454
+ }
455
+ resultMap.get(row.class_name).push(art);
456
+ }
457
+ return Array.from(resultMap.entries()).map(([className, artifacts]) => ({
458
+ className,
459
+ artifacts
460
+ }));
461
+ }
462
+ catch (e) {
463
+ console.error("Search implementations failed", e);
464
+ return [];
465
+ }
466
+ }
329
467
  /**
330
468
  * Retrieves an artifact by its database ID.
331
469
  */
@@ -59,29 +59,38 @@ export class SourceParser {
59
59
  // If it is the sources jar, decompilation will fail (as it doesn't contain .class files),
60
60
  // returning null.
61
61
  if (type === 'source' || type === 'docs') {
62
- return this.decompileClass(jarPath, className);
62
+ return this.decompileClass(jarPath, className, type);
63
63
  }
64
64
  return null;
65
65
  }
66
- static async decompileClass(jarPath, className) {
66
+ static async decompileClass(jarPath, className, type) {
67
+ const config = await Config.getInstance();
68
+ const cfrPath = config.getCfrJarPath();
69
+ if (!cfrPath || !fs.existsSync(cfrPath)) {
70
+ throw new Error(`CFR jar not found at ${cfrPath}`);
71
+ }
72
+ // Use java -cp cfr.jar:target.jar org.benf.cfr.reader.Main className
73
+ const classpath = `${cfrPath}${path.delimiter}${jarPath}`;
74
+ const cmd = `java -cp "${classpath}" org.benf.cfr.reader.Main "${className}"`;
75
+ // console.error("Running decompile command:", cmd);
67
76
  try {
68
- const config = await Config.getInstance();
69
- const cfrPath = config.getCfrJarPath();
70
- if (!cfrPath || !fs.existsSync(cfrPath)) {
71
- // If we can't find CFR, we can't decompile.
72
- return null;
77
+ const { stdout, stderr } = await execAsync(cmd);
78
+ if (!stdout && stderr) {
79
+ console.error(`CFR stderr for ${className}:`, stderr);
80
+ // If stderr has content but stdout is empty, it might be an error
81
+ throw new Error(`CFR stderr: ${stderr}`);
82
+ }
83
+ if (stdout) {
84
+ return this.parse(className, stdout, type);
73
85
  }
74
- // Use java -cp cfr.jar:target.jar org.benf.cfr.reader.Main className
75
- const classpath = `${cfrPath}${path.delimiter}${jarPath}`;
76
- const { stdout } = await execAsync(`java -cp "${classpath}" org.benf.cfr.reader.Main "${className}"`);
77
86
  return {
78
87
  className,
79
88
  source: stdout // Return as source
80
89
  };
81
90
  }
82
91
  catch (e) {
83
- // console.error(`CFR failed for ${className} in ${jarPath}:`, e);
84
- return null;
92
+ console.error(`CFR failed for ${className} in ${jarPath}:`, e.message);
93
+ throw e; // Rethrow to let caller handle
85
94
  }
86
95
  }
87
96
  static async getSignaturesWithJavap(jarPath, className) {
@@ -123,7 +132,8 @@ export class SourceParser {
123
132
  let inDoc = false;
124
133
  // Regex to match method signatures (public/protected, return type, name, args)
125
134
  // ignoring annotations for simplicity
126
- const methodRegex = /^\s*(public|protected)\s+(?:[\w<>?\[\]]+\s+)+(\w+)\s*\([^)]*\)\s*(?:throws\s+[\w,.\s]+)?\s*\{?/;
135
+ // Expanded to match decompiled code better (e.g., might not have throws, might be abstract)
136
+ const methodRegex = /^\s*(public|protected)\s+(?:[\w<>?\[\]]+\s+)+(\w+)\s*\([^)]*\)/;
127
137
  for (const line of lines) {
128
138
  const trimmed = line.trim();
129
139
  // Javadoc extraction
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maven-indexer-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for indexing local Maven repository",
5
5
  "main": "build/index.js",
6
6
  "type": "module",