maven-indexer-mcp 1.0.7 → 1.0.9

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/db/index.js CHANGED
@@ -1,13 +1,26 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import path from 'path';
3
+ import os from 'os';
4
+ import fs from 'fs';
3
5
  export class DB {
4
6
  static instance;
5
7
  db;
6
8
  constructor() {
7
9
  // Check environment variable for DB path (useful for testing)
8
- const dbName = process.env.DB_FILE || 'maven-index.sqlite';
9
- const dbPath = path.join(process.cwd(), dbName);
10
- this.db = new Database(dbPath);
10
+ if (process.env.DB_FILE) {
11
+ this.db = new Database(process.env.DB_FILE);
12
+ }
13
+ else {
14
+ // Use home directory for the database file
15
+ const homeDir = os.homedir();
16
+ const configDir = path.join(homeDir, '.maven-indexer-mcp');
17
+ // Ensure the directory exists
18
+ if (!fs.existsSync(configDir)) {
19
+ fs.mkdirSync(configDir, { recursive: true });
20
+ }
21
+ const dbPath = path.join(configDir, 'maven-index.sqlite');
22
+ this.db = new Database(dbPath);
23
+ }
11
24
  this.initSchema();
12
25
  }
13
26
  static getInstance() {
package/build/index.js CHANGED
@@ -20,6 +20,7 @@ const indexer = Indexer.getInstance();
20
20
  // We trigger indexing but don't await it so server can start
21
21
  indexer.index().then(() => {
22
22
  // Start watching for changes after initial index
23
+ indexer.startSchedule();
23
24
  return indexer.startWatch();
24
25
  }).catch(err => console.error("Initial indexing failed:", err));
25
26
  server.registerTool("get_class_details", {
@@ -154,9 +155,9 @@ server.registerTool("get_class_details", {
154
155
  return { content: [{ type: "text", text: results.join("\n\n") }] };
155
156
  });
156
157
  server.registerTool("search_artifacts", {
157
- description: "Search for internal company artifacts and libraries in the local Maven repository and Gradle caches by coordinate (groupId, artifactId) or keyword. Use this primarily for internal company packages or to find available versions of internal projects that are locally built. Also supports searching third-party libraries in the local cache. Supports batch queries.",
158
+ description: "Search for internal company artifacts and libraries in the local Maven repository and Gradle caches by coordinate (groupId, artifactId), keyword, or class name. Use this primarily for internal company packages or to find available versions of internal projects that are locally built. Also supports searching third-party libraries in the local cache. Supports batch queries.",
158
159
  inputSchema: z.object({
159
- query: z.string().optional().describe("Search query (groupId, artifactId, or keyword)"),
160
+ query: z.string().optional().describe("Search query (groupId, artifactId, keyword, or class name)"),
160
161
  queries: z.array(z.string()).optional().describe("Batch search queries"),
161
162
  }),
162
163
  }, async ({ query, queries }) => {
@@ -169,13 +170,34 @@ server.registerTool("search_artifacts", {
169
170
  return { content: [{ type: "text", text: "No query provided." }] };
170
171
  }
171
172
  const results = allQueries.map(q => {
172
- const matches = indexer.search(q);
173
+ let allMatches = [];
174
+ let searchType = "artifact";
175
+ // First try class search (for both simple and fully qualified class names)
176
+ const classSearchResults = indexer.searchClass(q);
177
+ if (classSearchResults.length > 0) {
178
+ // Extract unique artifacts from class search results
179
+ const artifactMap = new Map();
180
+ classSearchResults.forEach(result => {
181
+ result.artifacts.forEach(artifact => {
182
+ const key = `${artifact.groupId}:${artifact.artifactId}:${artifact.version}`;
183
+ if (!artifactMap.has(key)) {
184
+ artifactMap.set(key, artifact);
185
+ }
186
+ });
187
+ });
188
+ allMatches = Array.from(artifactMap.values());
189
+ searchType = "class";
190
+ }
191
+ else {
192
+ // Fallback to artifact search if class search finds nothing
193
+ allMatches = indexer.search(q);
194
+ }
173
195
  // Limit results to avoid overflow
174
- const limitedMatches = matches.slice(0, 20);
196
+ const limitedMatches = allMatches.slice(0, 20);
175
197
  const text = limitedMatches.length > 0
176
198
  ? limitedMatches.map(a => `${a.groupId}:${a.artifactId}:${a.version} (Has Source: ${a.hasSource})`).join("\n")
177
199
  : "No artifacts found matching the query.";
178
- return `### Results for "${q}" (Found ${matches.length}${matches.length > 20 ? ', showing first 20' : ''}):\n${text}`;
200
+ return `### Results for "${q}" (Found ${allMatches.length} via ${searchType} search${allMatches.length > 20 ? ', showing first 20' : ''}):\n${text}`;
179
201
  });
180
202
  return {
181
203
  content: [
package/build/indexer.js CHANGED
@@ -15,6 +15,7 @@ export class Indexer {
15
15
  isIndexing = false;
16
16
  watcher = null;
17
17
  debounceTimer = null;
18
+ scheduleTimer = null;
18
19
  constructor() {
19
20
  }
20
21
  static getInstance() {
@@ -25,54 +26,101 @@ export class Indexer {
25
26
  }
26
27
  /**
27
28
  * Starts watching the local repository for changes.
28
- * Debounces changes to trigger re-indexing.
29
29
  */
30
30
  async startWatch() {
31
31
  const config = await Config.getInstance();
32
- const pathsToWatch = [];
32
+ const watchPaths = [];
33
+ // Simple: Just add the main repository paths
33
34
  if (config.localRepository && fsSync.existsSync(config.localRepository)) {
34
- pathsToWatch.push(config.localRepository);
35
+ watchPaths.push(config.localRepository);
35
36
  }
36
37
  if (config.gradleRepository && fsSync.existsSync(config.gradleRepository)) {
37
- pathsToWatch.push(config.gradleRepository);
38
+ watchPaths.push(config.gradleRepository);
38
39
  }
39
- if (pathsToWatch.length === 0) {
40
+ if (watchPaths.length === 0) {
40
41
  console.error("No repository paths found, skipping watch mode.");
41
42
  return;
42
43
  }
43
44
  if (this.watcher) {
44
45
  return;
45
46
  }
46
- console.error(`Starting file watcher on ${pathsToWatch.join(', ')}...`);
47
- this.watcher = chokidar.watch(pathsToWatch, {
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
- },
55
- persistent: true,
56
- ignoreInitial: true,
57
- depth: 10 // Limit depth to avoid too much overhead? Standard maven repo depth is around 3-5
58
- });
59
- const onChange = () => {
60
- if (this.debounceTimer)
61
- clearTimeout(this.debounceTimer);
62
- this.debounceTimer = setTimeout(() => {
63
- console.error("Repository change detected. Triggering re-index...");
64
- this.index().catch(console.error);
65
- }, 5000); // Debounce for 5 seconds
66
- };
67
- this.watcher
68
- .on('add', (path) => {
69
- if (path.endsWith('.jar') || path.endsWith('.pom'))
70
- onChange();
71
- })
72
- .on('unlink', (path) => {
73
- if (path.endsWith('.jar') || path.endsWith('.pom'))
74
- onChange();
75
- });
47
+ console.error(`🔍 Starting file watcher on: ${watchPaths.join(', ')}`);
48
+ try {
49
+ // Watch for .jar and .pom files changes deep in the repository
50
+ this.watcher = chokidar.watch(watchPaths, {
51
+ // Watch for jar and pom files
52
+ ignored: [
53
+ /(^|[\/\\])\../, // ignore dotfiles
54
+ /node_modules/,
55
+ /target/,
56
+ /build/,
57
+ ],
58
+ persistent: true,
59
+ ignoreInitial: true,
60
+ awaitWriteFinish: {
61
+ stabilityThreshold: 2000,
62
+ pollInterval: 100
63
+ },
64
+ ignorePermissionErrors: true
65
+ });
66
+ // Watch for file additions and changes
67
+ this.watcher
68
+ .on('add', (filePath) => {
69
+ if (filePath.endsWith('.jar') || filePath.endsWith('.pom')) {
70
+ console.error(`📄 New file detected: ${path.basename(filePath)}`);
71
+ this.triggerReindex();
72
+ }
73
+ })
74
+ .on('addDir', (dirPath) => {
75
+ console.error(`📁 New directory detected: ${path.basename(dirPath)}`);
76
+ this.triggerReindex();
77
+ })
78
+ .on('unlink', (filePath) => {
79
+ if (filePath.endsWith('.jar') || filePath.endsWith('.pom')) {
80
+ console.error(`🗑️ File removed: ${path.basename(filePath)}`);
81
+ this.triggerReindex();
82
+ }
83
+ })
84
+ .on('unlinkDir', (dirPath) => {
85
+ console.error(`🗑️ Directory removed: ${path.basename(dirPath)}`);
86
+ this.triggerReindex();
87
+ })
88
+ .on('error', (error) => {
89
+ const errorMessage = error instanceof Error ? error.message : String(error);
90
+ console.error(`❌ Watcher error: ${errorMessage}`);
91
+ });
92
+ console.error('✅ File watcher started successfully');
93
+ }
94
+ catch (error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error);
96
+ console.error(`❌ Failed to start watcher: ${errorMessage}`);
97
+ }
98
+ }
99
+ /**
100
+ * Starts a scheduled job to reindex every 1 hour.
101
+ */
102
+ startSchedule() {
103
+ if (this.scheduleTimer) {
104
+ return;
105
+ }
106
+ console.error('⏰ Starting scheduled reindex job (every 1 hour)...');
107
+ this.scheduleTimer = setInterval(() => {
108
+ console.error('⏰ Scheduled reindex triggered...');
109
+ this.index().catch(console.error);
110
+ }, 3600000); // 1 hour
111
+ }
112
+ /**
113
+ * Trigger reindexing with debouncing (wait a bit for multiple changes)
114
+ */
115
+ triggerReindex() {
116
+ if (this.debounceTimer) {
117
+ clearTimeout(this.debounceTimer);
118
+ }
119
+ // Wait 3 seconds after the last change before reindexing
120
+ this.debounceTimer = setTimeout(() => {
121
+ console.error('🔄 Changes detected - triggering reindex...');
122
+ this.index().catch(console.error);
123
+ }, 3000);
76
124
  }
77
125
  /**
78
126
  * Forces a full re-index of the repository.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * SIMPLIFIED FILE WATCHER for Maven Indexer
3
+ *
4
+ * This is a cleaned-up version that's easier to understand.
5
+ * It watches for new JAR and POM files in your Maven/Gradle repositories.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ export class SimpleWatcher {
10
+ watcher = null;
11
+ onChangeCallback;
12
+ constructor(onChangeCallback) {
13
+ this.onChangeCallback = onChangeCallback;
14
+ }
15
+ /**
16
+ * Start watching - SIMPLE VERSION
17
+ * Just watch the main folders and filter for JAR/POM files
18
+ */
19
+ async startWatching(mavenRepoPath, gradleRepoPath) {
20
+ if (this.watcher) {
21
+ console.log('Watcher already running');
22
+ return;
23
+ }
24
+ const watchPaths = [];
25
+ // Add Maven repo if it exists
26
+ if (mavenRepoPath && fs.existsSync(mavenRepoPath)) {
27
+ watchPaths.push(mavenRepoPath);
28
+ }
29
+ // Add Gradle repo if it exists
30
+ if (gradleRepoPath && fs.existsSync(gradleRepoPath)) {
31
+ watchPaths.push(gradleRepoPath);
32
+ }
33
+ if (watchPaths.length === 0) {
34
+ console.log('No repository paths to watch');
35
+ return;
36
+ }
37
+ console.log(`🔍 Watching for new JAR/POM files in: ${watchPaths.join(', ')}`);
38
+ // SIMPLE: Use chokidar with basic ignore patterns
39
+ const chokidar = await import('chokidar');
40
+ this.watcher = chokidar.watch(watchPaths, {
41
+ // Ignore common junk files (simple patterns)
42
+ ignored: [
43
+ '**/.*', // Hidden files
44
+ '**/*.tmp', // Temp files
45
+ '**/*.sha1', // Checksum files
46
+ '**/*.sha256', // Checksum files
47
+ '**/*.md5', // Checksum files
48
+ ],
49
+ // Don't watch too deep (Maven repos are usually 3-4 levels)
50
+ depth: 6,
51
+ // Don't trigger for files that already exist
52
+ ignoreInitial: true,
53
+ // Wait for files to finish writing before triggering
54
+ awaitWriteFinish: {
55
+ stabilityThreshold: 2000, // Wait 2 seconds after file stops changing
56
+ pollInterval: 100
57
+ }
58
+ });
59
+ // SIMPLE: Just watch for new files being added
60
+ this.watcher
61
+ .on('add', (filePath) => {
62
+ // Only care about JAR and POM files
63
+ if (filePath.endsWith('.jar') || filePath.endsWith('.pom')) {
64
+ console.log(`📦 New file detected: ${path.basename(filePath)}`);
65
+ this.triggerReindex();
66
+ }
67
+ })
68
+ .on('unlink', (filePath) => {
69
+ // Also watch for files being deleted
70
+ if (filePath.endsWith('.jar') || filePath.endsWith('.pom')) {
71
+ console.log(`🗑️ File removed: ${path.basename(filePath)}`);
72
+ this.triggerReindex();
73
+ }
74
+ })
75
+ .on('error', (error) => {
76
+ console.log(`❌ Watcher error: ${error.message}`);
77
+ });
78
+ console.log('✅ File watcher started successfully');
79
+ }
80
+ /**
81
+ * Stop watching
82
+ */
83
+ stopWatching() {
84
+ if (this.watcher) {
85
+ this.watcher.close();
86
+ this.watcher = null;
87
+ console.log('🛑 File watcher stopped');
88
+ }
89
+ }
90
+ /**
91
+ * Trigger reindexing with debouncing (wait a bit for multiple changes)
92
+ */
93
+ debounceTimer = null;
94
+ triggerReindex() {
95
+ // Clear any existing timer
96
+ if (this.debounceTimer) {
97
+ clearTimeout(this.debounceTimer);
98
+ }
99
+ // Wait 3 seconds after the last change before reindexing
100
+ this.debounceTimer = setTimeout(() => {
101
+ console.log('🔄 Changes detected - triggering reindex...');
102
+ this.onChangeCallback();
103
+ }, 3000);
104
+ }
105
+ }
106
+ /**
107
+ * USAGE EXAMPLE:
108
+ *
109
+ * const watcher = new SimpleWatcher(() => {
110
+ * console.log('Time to reindex the repository!');
111
+ * // Put your reindexing code here
112
+ * });
113
+ *
114
+ * await watcher.startWatching(
115
+ * '/Users/tangcent/.m2/repository',
116
+ * '/Users/tangcent/.gradle/caches/modules-2/files-2.1'
117
+ * );
118
+ *
119
+ * // Later... when you want to stop
120
+ * watcher.stopWatching();
121
+ **/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maven-indexer-mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "MCP server for indexing local Maven repository",
5
5
  "main": "build/index.js",
6
6
  "type": "module",