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 +16 -3
- package/build/index.js +27 -5
- package/build/indexer.js +83 -35
- package/build/simple_watcher.js +121 -0
- package/package.json +1 -1
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 ${
|
|
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
|
|
32
|
+
const watchPaths = [];
|
|
33
|
+
// Simple: Just add the main repository paths
|
|
33
34
|
if (config.localRepository && fsSync.existsSync(config.localRepository)) {
|
|
34
|
-
|
|
35
|
+
watchPaths.push(config.localRepository);
|
|
35
36
|
}
|
|
36
37
|
if (config.gradleRepository && fsSync.existsSync(config.gradleRepository)) {
|
|
37
|
-
|
|
38
|
+
watchPaths.push(config.gradleRepository);
|
|
38
39
|
}
|
|
39
|
-
if (
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
**/
|