@crowley/rag-mcp 1.0.2 → 1.0.4
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/dist/formatters.d.ts +13 -0
- package/dist/formatters.js +20 -0
- package/dist/tools/indexing.d.ts +4 -0
- package/dist/tools/indexing.js +116 -18
- package/dist/tools/search.js +18 -38
- package/package.json +3 -2
package/dist/formatters.d.ts
CHANGED
|
@@ -26,6 +26,19 @@ export declare function formatMemoryResults(results: Array<{
|
|
|
26
26
|
memory: Record<string, unknown>;
|
|
27
27
|
score: number;
|
|
28
28
|
}>, emptyMessage?: string): string;
|
|
29
|
+
/** Format navigate-mode search results as compact navigation pointers */
|
|
30
|
+
export declare function formatNavigationResults(results: Array<{
|
|
31
|
+
file: string;
|
|
32
|
+
lines?: [number, number];
|
|
33
|
+
symbols?: string[];
|
|
34
|
+
imports?: string[];
|
|
35
|
+
connections?: string[];
|
|
36
|
+
layer?: string;
|
|
37
|
+
service?: string;
|
|
38
|
+
preview?: string;
|
|
39
|
+
score: number;
|
|
40
|
+
graphExpanded?: boolean;
|
|
41
|
+
}>): string;
|
|
29
42
|
/** Format a simple list of files with scores */
|
|
30
43
|
export declare function formatFileList(files: Array<{
|
|
31
44
|
file: string;
|
package/dist/formatters.js
CHANGED
|
@@ -60,6 +60,26 @@ export function formatMemoryResults(results, emptyMessage = "No memories found."
|
|
|
60
60
|
});
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
|
+
/** Format navigate-mode search results as compact navigation pointers */
|
|
64
|
+
export function formatNavigationResults(results) {
|
|
65
|
+
if (!results?.length)
|
|
66
|
+
return "No results found.";
|
|
67
|
+
return results.map(r => {
|
|
68
|
+
const loc = r.lines ? `:${r.lines[0]}-${r.lines[1]}` : '';
|
|
69
|
+
let out = `**${r.file}${loc}** (${pct(r.score)})`;
|
|
70
|
+
if (r.layer)
|
|
71
|
+
out += ` [${r.layer}]`;
|
|
72
|
+
if (r.graphExpanded)
|
|
73
|
+
out += ' _(graph)_';
|
|
74
|
+
if (r.preview)
|
|
75
|
+
out += `\n\`${truncate(r.preview, 100)}\``;
|
|
76
|
+
if (r.symbols?.length)
|
|
77
|
+
out += `\nSymbols: ${r.symbols.join(', ')}`;
|
|
78
|
+
if (r.connections?.length)
|
|
79
|
+
out += `\nConnections: ${r.connections.map(c => '`' + c + '`').join(', ')}`;
|
|
80
|
+
return out;
|
|
81
|
+
}).join('\n\n');
|
|
82
|
+
}
|
|
63
83
|
/** Format a simple list of files with scores */
|
|
64
84
|
export function formatFileList(files, emptyMessage = "No files found.") {
|
|
65
85
|
if (!files || files.length === 0)
|
package/dist/tools/indexing.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Indexing tools module - codebase indexing, status, zero-downtime reindex,
|
|
3
3
|
* and alias management.
|
|
4
|
+
*
|
|
5
|
+
* index_codebase and reindex_zero_downtime read files locally and upload
|
|
6
|
+
* them to the RAG API in batches via POST /api/index/upload. This allows
|
|
7
|
+
* remote MCP clients to index codebases that aren't on the server filesystem.
|
|
4
8
|
*/
|
|
5
9
|
import type { ToolModule } from "../types.js";
|
|
6
10
|
/**
|
package/dist/tools/indexing.js
CHANGED
|
@@ -1,7 +1,105 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Indexing tools module - codebase indexing, status, zero-downtime reindex,
|
|
3
3
|
* and alias management.
|
|
4
|
+
*
|
|
5
|
+
* index_codebase and reindex_zero_downtime read files locally and upload
|
|
6
|
+
* them to the RAG API in batches via POST /api/index/upload. This allows
|
|
7
|
+
* remote MCP clients to index codebases that aren't on the server filesystem.
|
|
4
8
|
*/
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { glob } from "glob";
|
|
12
|
+
const DEFAULT_PATTERNS = [
|
|
13
|
+
"**/*.ts",
|
|
14
|
+
"**/*.tsx",
|
|
15
|
+
"**/*.js",
|
|
16
|
+
"**/*.jsx",
|
|
17
|
+
"**/*.vue",
|
|
18
|
+
"**/*.py",
|
|
19
|
+
"**/*.go",
|
|
20
|
+
"**/*.rs",
|
|
21
|
+
"**/*.java",
|
|
22
|
+
"**/*.md",
|
|
23
|
+
"**/*.sql",
|
|
24
|
+
"**/*.yml",
|
|
25
|
+
"**/*.yaml",
|
|
26
|
+
"**/Dockerfile",
|
|
27
|
+
];
|
|
28
|
+
const DEFAULT_EXCLUDE = [
|
|
29
|
+
"**/node_modules/**",
|
|
30
|
+
"**/dist/**",
|
|
31
|
+
"**/build/**",
|
|
32
|
+
"**/.git/**",
|
|
33
|
+
"**/coverage/**",
|
|
34
|
+
"**/.nuxt/**",
|
|
35
|
+
"**/.next/**",
|
|
36
|
+
"**/vendor/**",
|
|
37
|
+
"**/__pycache__/**",
|
|
38
|
+
"**/target/**",
|
|
39
|
+
"**/package-lock.json",
|
|
40
|
+
"**/yarn.lock",
|
|
41
|
+
"**/pnpm-lock.yaml",
|
|
42
|
+
"**/eval/results/**",
|
|
43
|
+
"**/eval/golden-queries.json",
|
|
44
|
+
];
|
|
45
|
+
const BATCH_SIZE = 50;
|
|
46
|
+
/**
|
|
47
|
+
* Discover files locally using glob, read their contents, and upload
|
|
48
|
+
* them to the RAG API in batches.
|
|
49
|
+
*/
|
|
50
|
+
async function uploadFiles(ctx, projectPath, opts) {
|
|
51
|
+
const patterns = opts.patterns || DEFAULT_PATTERNS;
|
|
52
|
+
const excludePatterns = opts.excludePatterns || DEFAULT_EXCLUDE;
|
|
53
|
+
// Discover files locally
|
|
54
|
+
const files = await glob(patterns, {
|
|
55
|
+
cwd: projectPath,
|
|
56
|
+
ignore: excludePatterns,
|
|
57
|
+
nodir: true,
|
|
58
|
+
absolute: false,
|
|
59
|
+
});
|
|
60
|
+
if (files.length === 0) {
|
|
61
|
+
return { totalFiles: 0, indexedFiles: 0, totalChunks: 0, errors: 0, duration: 0 };
|
|
62
|
+
}
|
|
63
|
+
let totalIndexed = 0;
|
|
64
|
+
let totalChunks = 0;
|
|
65
|
+
let totalErrors = 0;
|
|
66
|
+
let totalDuration = 0;
|
|
67
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
68
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
69
|
+
const filePayloads = [];
|
|
70
|
+
for (const relPath of batch) {
|
|
71
|
+
try {
|
|
72
|
+
const absPath = path.join(projectPath, relPath);
|
|
73
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
74
|
+
filePayloads.push({ path: relPath, content });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
totalErrors++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (filePayloads.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
const isFirst = i === 0;
|
|
83
|
+
const isLast = i + BATCH_SIZE >= files.length;
|
|
84
|
+
const response = await ctx.api.post("/api/index/upload", {
|
|
85
|
+
files: filePayloads,
|
|
86
|
+
force: isFirst && (opts.force ?? false),
|
|
87
|
+
done: isLast,
|
|
88
|
+
});
|
|
89
|
+
const data = response.data;
|
|
90
|
+
totalIndexed += data.filesProcessed || 0;
|
|
91
|
+
totalChunks += data.chunksCreated || 0;
|
|
92
|
+
totalErrors += data.errors || 0;
|
|
93
|
+
totalDuration += data.duration || 0;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
totalFiles: files.length,
|
|
97
|
+
indexedFiles: totalIndexed,
|
|
98
|
+
totalChunks,
|
|
99
|
+
errors: totalErrors,
|
|
100
|
+
duration: totalDuration,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
5
103
|
/**
|
|
6
104
|
* Create the indexing tools module with project-specific descriptions.
|
|
7
105
|
*/
|
|
@@ -67,16 +165,15 @@ export function createIndexingTools(projectName) {
|
|
|
67
165
|
];
|
|
68
166
|
const handlers = {
|
|
69
167
|
index_codebase: async (args, ctx) => {
|
|
70
|
-
const { path, force = false } = args;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
path: path || ctx.projectPath,
|
|
74
|
-
force,
|
|
75
|
-
});
|
|
76
|
-
const data = response.data;
|
|
168
|
+
const { path: indexPath, force = false } = args;
|
|
169
|
+
const projectPath = indexPath || ctx.projectPath;
|
|
170
|
+
const stats = await uploadFiles(ctx, projectPath, { force });
|
|
77
171
|
let result = `## Indexing ${projectName}\n\n`;
|
|
78
|
-
result += `- **
|
|
79
|
-
result += `- **Files
|
|
172
|
+
result += `- **Total files found:** ${stats.totalFiles}\n`;
|
|
173
|
+
result += `- **Files indexed:** ${stats.indexedFiles}\n`;
|
|
174
|
+
result += `- **Chunks created:** ${stats.totalChunks}\n`;
|
|
175
|
+
result += `- **Errors:** ${stats.errors}\n`;
|
|
176
|
+
result += `- **Duration:** ${stats.duration}ms\n`;
|
|
80
177
|
return result;
|
|
81
178
|
},
|
|
82
179
|
get_index_status: async (_args, ctx) => {
|
|
@@ -91,18 +188,19 @@ export function createIndexingTools(projectName) {
|
|
|
91
188
|
return result;
|
|
92
189
|
},
|
|
93
190
|
reindex_zero_downtime: async (args, ctx) => {
|
|
94
|
-
const { path, patterns, excludePatterns } = args;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
path: path || ctx.projectPath,
|
|
191
|
+
const { path: indexPath, patterns, excludePatterns } = args;
|
|
192
|
+
const projectPath = indexPath || ctx.projectPath;
|
|
193
|
+
const stats = await uploadFiles(ctx, projectPath, {
|
|
98
194
|
patterns,
|
|
99
195
|
excludePatterns,
|
|
196
|
+
force: true,
|
|
100
197
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
result += `- **
|
|
104
|
-
result += `- **
|
|
105
|
-
result += `- **
|
|
198
|
+
let result = `## Reindex: ${projectName}\n\n`;
|
|
199
|
+
result += `- **Total files found:** ${stats.totalFiles}\n`;
|
|
200
|
+
result += `- **Files indexed:** ${stats.indexedFiles}\n`;
|
|
201
|
+
result += `- **Chunks created:** ${stats.totalChunks}\n`;
|
|
202
|
+
result += `- **Errors:** ${stats.errors}\n`;
|
|
203
|
+
result += `- **Duration:** ${stats.duration}ms\n`;
|
|
106
204
|
return result;
|
|
107
205
|
},
|
|
108
206
|
list_aliases: async (_args, ctx) => {
|
package/dist/tools/search.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Search tools module - codebase search, similarity search, grouped/hybrid search,
|
|
3
3
|
* documentation search, and project statistics.
|
|
4
4
|
*/
|
|
5
|
-
import { formatCodeResults,
|
|
5
|
+
import { formatCodeResults, formatNavigationResults, truncate } from "../formatters.js";
|
|
6
6
|
/**
|
|
7
7
|
* Create the search tools module with project-specific descriptions.
|
|
8
8
|
*/
|
|
@@ -10,7 +10,7 @@ export function createSearchTools(projectName) {
|
|
|
10
10
|
const tools = [
|
|
11
11
|
{
|
|
12
12
|
name: "search_codebase",
|
|
13
|
-
description: `Search the ${projectName} codebase
|
|
13
|
+
description: `Search the ${projectName} codebase. Returns file locations, symbols, and graph connections. Use Read tool to view the actual code at returned locations.`,
|
|
14
14
|
inputSchema: {
|
|
15
15
|
type: "object",
|
|
16
16
|
properties: {
|
|
@@ -64,7 +64,7 @@ export function createSearchTools(projectName) {
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
name: "grouped_search",
|
|
67
|
-
description: `Search ${projectName} codebase
|
|
67
|
+
description: `Search ${projectName} codebase grouped by file. Returns file locations with symbols and connections. Use Read tool to view the actual code.`,
|
|
68
68
|
inputSchema: {
|
|
69
69
|
type: "object",
|
|
70
70
|
properties: {
|
|
@@ -100,7 +100,7 @@ export function createSearchTools(projectName) {
|
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
name: "hybrid_search",
|
|
103
|
-
description: `Hybrid search combining keyword matching and semantic similarity for ${projectName}.
|
|
103
|
+
description: `Hybrid search combining keyword matching and semantic similarity for ${projectName}. Returns file locations with symbols and connections. Use Read tool to view code.`,
|
|
104
104
|
inputSchema: {
|
|
105
105
|
type: "object",
|
|
106
106
|
properties: {
|
|
@@ -186,7 +186,7 @@ export function createSearchTools(projectName) {
|
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
name: "search_graph",
|
|
189
|
-
description: `Search ${projectName} codebase with graph expansion.
|
|
189
|
+
description: `Search ${projectName} codebase with graph expansion. Returns file locations plus connected files via import/call relationships. Use Read tool to view code.`,
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: "object",
|
|
192
192
|
properties: {
|
|
@@ -216,13 +216,14 @@ export function createSearchTools(projectName) {
|
|
|
216
216
|
collection: `${ctx.collectionPrefix}codebase`,
|
|
217
217
|
query,
|
|
218
218
|
limit,
|
|
219
|
+
mode: "navigate",
|
|
219
220
|
filters: { language, path, layer, service },
|
|
220
221
|
});
|
|
221
222
|
const results = response.data.results;
|
|
222
223
|
if (!results || results.length === 0) {
|
|
223
224
|
return "No results found for this query.";
|
|
224
225
|
}
|
|
225
|
-
return
|
|
226
|
+
return formatNavigationResults(results);
|
|
226
227
|
},
|
|
227
228
|
search_similar: async (args, ctx) => {
|
|
228
229
|
const { code, limit = 5 } = args;
|
|
@@ -246,23 +247,15 @@ export function createSearchTools(projectName) {
|
|
|
246
247
|
query,
|
|
247
248
|
groupBy,
|
|
248
249
|
limit,
|
|
250
|
+
mode: "navigate",
|
|
249
251
|
filters: hasFilters ? filters : undefined,
|
|
250
252
|
});
|
|
251
253
|
const groups = response.data.groups;
|
|
252
254
|
if (!groups || groups.length === 0) {
|
|
253
255
|
return "No results found.";
|
|
254
256
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const r = g.results[0];
|
|
258
|
-
return (`**${g[groupBy]}** (score: ${pct(r.score)})\n` +
|
|
259
|
-
"```" +
|
|
260
|
-
(r.language || "") +
|
|
261
|
-
"\n" +
|
|
262
|
-
truncate(r.content, 300) +
|
|
263
|
-
"\n```");
|
|
264
|
-
})
|
|
265
|
-
.join("\n\n---\n\n");
|
|
257
|
+
const allResults = groups.flatMap((g) => g.results);
|
|
258
|
+
return formatNavigationResults(allResults);
|
|
266
259
|
},
|
|
267
260
|
hybrid_search: async (args, ctx) => {
|
|
268
261
|
const { query, limit = 10, semanticWeight = 0.7, language, layer, service } = args;
|
|
@@ -273,20 +266,14 @@ export function createSearchTools(projectName) {
|
|
|
273
266
|
query,
|
|
274
267
|
limit,
|
|
275
268
|
semanticWeight,
|
|
269
|
+
mode: "navigate",
|
|
276
270
|
filters: hasFilters ? filters : undefined,
|
|
277
271
|
});
|
|
278
272
|
const results = response.data.results;
|
|
279
273
|
if (!results || results.length === 0) {
|
|
280
274
|
return "No results found.";
|
|
281
275
|
}
|
|
282
|
-
return results
|
|
283
|
-
.map((r) => `**${r.file}** (combined: ${pct(r.score)}${r.semanticScore != null ? `, semantic: ${pct(r.semanticScore)}` : ''}${r.keywordScore != null ? `, keyword: ${pct(r.keywordScore)}` : ''})\n` +
|
|
284
|
-
"```" +
|
|
285
|
-
(r.language || "") +
|
|
286
|
-
"\n" +
|
|
287
|
-
truncate(r.content, 300) +
|
|
288
|
-
"\n```")
|
|
289
|
-
.join("\n\n---\n\n");
|
|
276
|
+
return formatNavigationResults(results);
|
|
290
277
|
},
|
|
291
278
|
search_docs: async (args, ctx) => {
|
|
292
279
|
const { query, limit = 5 } = args;
|
|
@@ -329,27 +316,20 @@ export function createSearchTools(projectName) {
|
|
|
329
316
|
query,
|
|
330
317
|
limit,
|
|
331
318
|
expandHops,
|
|
319
|
+
mode: "navigate",
|
|
332
320
|
});
|
|
333
321
|
const { results, graphExpanded, expandedFiles } = response.data;
|
|
322
|
+
if ((!results || results.length === 0) && (!graphExpanded || graphExpanded.length === 0)) {
|
|
323
|
+
return "No results found.";
|
|
324
|
+
}
|
|
334
325
|
let output = "";
|
|
335
326
|
if (results && results.length > 0) {
|
|
336
327
|
output += "**Direct matches:**\n\n";
|
|
337
|
-
output += results
|
|
338
|
-
.map((r) => `**${r.file}** (score: ${pct(r.score)})\n` +
|
|
339
|
-
"```" + (r.language || "") + "\n" +
|
|
340
|
-
truncate(r.content, 300) + "\n```")
|
|
341
|
-
.join("\n\n");
|
|
328
|
+
output += formatNavigationResults(results);
|
|
342
329
|
}
|
|
343
330
|
if (graphExpanded && graphExpanded.length > 0) {
|
|
344
331
|
output += "\n\n---\n\n**Graph-connected files:**\n\n";
|
|
345
|
-
output += graphExpanded
|
|
346
|
-
.map((r) => `**${r.file}** (score: ${pct(r.score)})\n` +
|
|
347
|
-
"```" + (r.language || "") + "\n" +
|
|
348
|
-
truncate(r.content, 300) + "\n```")
|
|
349
|
-
.join("\n\n");
|
|
350
|
-
}
|
|
351
|
-
if (!output) {
|
|
352
|
-
return "No results found.";
|
|
332
|
+
output += formatNavigationResults(graphExpanded);
|
|
353
333
|
}
|
|
354
334
|
if (expandedFiles && expandedFiles.length > 0) {
|
|
355
335
|
output += `\n\n_Graph expanded to ${expandedFiles.length} additional files._`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowley/rag-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Universal RAG MCP Server for any project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
34
|
-
"axios": "^1.6.0"
|
|
34
|
+
"axios": "^1.6.0",
|
|
35
|
+
"glob": "^11.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/node": "^20.10.0",
|