@crowley/rag-mcp 1.0.3 → 1.0.5
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/context-enrichment.d.ts +2 -2
- package/dist/context-enrichment.js +30 -14
- package/dist/index.js +18 -0
- package/dist/tools/architecture.js +8 -0
- package/dist/tools/indexing.d.ts +4 -0
- package/dist/tools/indexing.js +129 -19
- package/dist/tools/suggestions.js +276 -0
- package/package.json +3 -2
|
@@ -26,9 +26,9 @@ export declare class ContextEnricher {
|
|
|
26
26
|
*/
|
|
27
27
|
before(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<string | null>;
|
|
28
28
|
/**
|
|
29
|
-
* After hook: fire-and-forget session activity tracking.
|
|
29
|
+
* After hook: fire-and-forget session activity tracking + implicit feedback.
|
|
30
30
|
*/
|
|
31
|
-
after(name: string,
|
|
31
|
+
after(name: string, args: Record<string, unknown>, result: string, ctx: ToolContext): void;
|
|
32
32
|
/**
|
|
33
33
|
* Extract a semantic query string from tool arguments.
|
|
34
34
|
*/
|
|
@@ -18,6 +18,7 @@ export const DEFAULT_ENRICHABLE_TOOLS = new Set([
|
|
|
18
18
|
"suggest_implementation",
|
|
19
19
|
"suggest_related_code",
|
|
20
20
|
"check_architecture",
|
|
21
|
+
"context_briefing",
|
|
21
22
|
"run_agent",
|
|
22
23
|
]);
|
|
23
24
|
export const DEFAULT_SKIP_TOOLS = new Set([
|
|
@@ -79,21 +80,36 @@ export class ContextEnricher {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
|
-
* After hook: fire-and-forget session activity tracking.
|
|
83
|
+
* After hook: fire-and-forget session activity tracking + implicit feedback.
|
|
83
84
|
*/
|
|
84
|
-
after(name,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
after(name, args, result, ctx) {
|
|
86
|
+
// Session activity tracking
|
|
87
|
+
if (ctx.activeSessionId) {
|
|
88
|
+
ctx.api
|
|
89
|
+
.post(`/api/session/${ctx.activeSessionId}/activity`, {
|
|
90
|
+
projectName: ctx.projectName,
|
|
91
|
+
type: "tool",
|
|
92
|
+
value: name,
|
|
93
|
+
})
|
|
94
|
+
.catch(() => { });
|
|
95
|
+
}
|
|
96
|
+
// Implicit positive feedback for enrichable search tools
|
|
97
|
+
if (this.config.enrichableTools.has(name)) {
|
|
98
|
+
const query = this.extractQuery(args);
|
|
99
|
+
if (query &&
|
|
100
|
+
result &&
|
|
101
|
+
!result.includes("No results") &&
|
|
102
|
+
!result.includes("not found") &&
|
|
103
|
+
!result.includes("No relevant context found")) {
|
|
104
|
+
ctx.api
|
|
105
|
+
.post("/api/feedback/search", {
|
|
106
|
+
projectName: ctx.projectName,
|
|
107
|
+
query,
|
|
108
|
+
feedbackType: "helpful",
|
|
109
|
+
})
|
|
110
|
+
.catch(() => { });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
/**
|
|
99
115
|
* Extract a semantic query string from tool arguments.
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
99
99
|
content: [{ type: "text", text: result }],
|
|
100
100
|
};
|
|
101
101
|
});
|
|
102
|
+
// Graceful shutdown: close active session on exit
|
|
103
|
+
async function cleanup() {
|
|
104
|
+
if (ctx.activeSessionId) {
|
|
105
|
+
try {
|
|
106
|
+
await api.post(`/api/session/${ctx.activeSessionId}/end`, {
|
|
107
|
+
projectName: PROJECT_NAME,
|
|
108
|
+
summary: "Session ended by MCP server shutdown",
|
|
109
|
+
autoSaveLearnings: true,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Best-effort, don't block shutdown
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
process.on("SIGINT", cleanup);
|
|
119
|
+
process.on("SIGTERM", cleanup);
|
|
102
120
|
// Start server
|
|
103
121
|
async function main() {
|
|
104
122
|
const transport = new StdioServerTransport();
|
|
@@ -291,6 +291,7 @@ ${alternatives ? `## Alternatives Considered\n${alternatives}` : ""}`;
|
|
|
291
291
|
query: query || "architecture decision ADR",
|
|
292
292
|
type: "decision",
|
|
293
293
|
limit,
|
|
294
|
+
tag: "adr",
|
|
294
295
|
});
|
|
295
296
|
const results = response.data.results || [];
|
|
296
297
|
const adrs = results.filter((r) => r.memory.tags?.includes("adr") &&
|
|
@@ -349,6 +350,7 @@ ${appliesTo ? `## Applies To\n${appliesTo}` : ""}`;
|
|
|
349
350
|
query: query || "architectural pattern structure",
|
|
350
351
|
type: "context",
|
|
351
352
|
limit,
|
|
353
|
+
tag: "pattern",
|
|
352
354
|
});
|
|
353
355
|
const results = response.data.results || [];
|
|
354
356
|
const patterns = results.filter((r) => {
|
|
@@ -383,6 +385,7 @@ ${appliesTo ? `## Applies To\n${appliesTo}` : ""}`;
|
|
|
383
385
|
query: patternQuery,
|
|
384
386
|
type: "context",
|
|
385
387
|
limit: 5,
|
|
388
|
+
tag: "pattern",
|
|
386
389
|
});
|
|
387
390
|
// Get relevant ADRs
|
|
388
391
|
const adrsResponse = await ctx.api.post("/api/memory/recall", {
|
|
@@ -390,6 +393,7 @@ ${appliesTo ? `## Applies To\n${appliesTo}` : ""}`;
|
|
|
390
393
|
query: patternQuery,
|
|
391
394
|
type: "decision",
|
|
392
395
|
limit: 5,
|
|
396
|
+
tag: "adr",
|
|
393
397
|
});
|
|
394
398
|
// Search similar code in codebase
|
|
395
399
|
let similarCode = [];
|
|
@@ -499,6 +503,7 @@ Provide a structured analysis:
|
|
|
499
503
|
query: `${type} ${feature} pattern structure`,
|
|
500
504
|
type: "context",
|
|
501
505
|
limit: 5,
|
|
506
|
+
tag: "pattern",
|
|
502
507
|
});
|
|
503
508
|
// Get relevant ADRs
|
|
504
509
|
const adrsResponse = await ctx.api.post("/api/memory/recall", {
|
|
@@ -506,6 +511,7 @@ Provide a structured analysis:
|
|
|
506
511
|
query: `${type} ${feature}`,
|
|
507
512
|
type: "decision",
|
|
508
513
|
limit: 3,
|
|
514
|
+
tag: "adr",
|
|
509
515
|
});
|
|
510
516
|
// Get similar implementations
|
|
511
517
|
const codeResponse = await ctx.api.post("/api/search", {
|
|
@@ -602,6 +608,7 @@ ${relatedAdr ? `## Related ADR\n${relatedAdr}` : ""}`;
|
|
|
602
608
|
query: "technical debt violation issue",
|
|
603
609
|
type: "insight",
|
|
604
610
|
limit: limit * 2, // Fetch extra to account for filtering
|
|
611
|
+
tag: "tech-debt",
|
|
605
612
|
});
|
|
606
613
|
const results = response.data.results || [];
|
|
607
614
|
const debts = results
|
|
@@ -649,6 +656,7 @@ ${relatedAdr ? `## Related ADR\n${relatedAdr}` : ""}`;
|
|
|
649
656
|
query: "pattern structure organization",
|
|
650
657
|
type: "context",
|
|
651
658
|
limit: 10,
|
|
659
|
+
tag: "pattern",
|
|
652
660
|
});
|
|
653
661
|
// Get codebase structure
|
|
654
662
|
const codeResponse = await ctx.api.post("/api/search", {
|
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,108 @@
|
|
|
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
|
+
}
|
|
103
|
+
// In-memory cache for get_index_status (30 min TTL)
|
|
104
|
+
let _statusCache = null;
|
|
105
|
+
const STATUS_CACHE_TTL = 30 * 60 * 1000; // 30 minutes
|
|
5
106
|
/**
|
|
6
107
|
* Create the indexing tools module with project-specific descriptions.
|
|
7
108
|
*/
|
|
@@ -27,7 +128,7 @@ export function createIndexingTools(projectName) {
|
|
|
27
128
|
},
|
|
28
129
|
{
|
|
29
130
|
name: "get_index_status",
|
|
30
|
-
description: `Get the indexing status for ${projectName} codebase.`,
|
|
131
|
+
description: `Get the indexing status for ${projectName} codebase. Results cached for 30 minutes.`,
|
|
31
132
|
inputSchema: {
|
|
32
133
|
type: "object",
|
|
33
134
|
properties: {},
|
|
@@ -67,19 +168,24 @@ export function createIndexingTools(projectName) {
|
|
|
67
168
|
];
|
|
68
169
|
const handlers = {
|
|
69
170
|
index_codebase: async (args, ctx) => {
|
|
70
|
-
const { path, force = false } = args;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
force,
|
|
75
|
-
});
|
|
76
|
-
const data = response.data;
|
|
171
|
+
const { path: indexPath, force = false } = args;
|
|
172
|
+
const projectPath = indexPath || ctx.projectPath;
|
|
173
|
+
const stats = await uploadFiles(ctx, projectPath, { force });
|
|
174
|
+
_statusCache = null; // Invalidate status cache after indexing
|
|
77
175
|
let result = `## Indexing ${projectName}\n\n`;
|
|
78
|
-
result += `- **
|
|
79
|
-
result += `- **Files
|
|
176
|
+
result += `- **Total files found:** ${stats.totalFiles}\n`;
|
|
177
|
+
result += `- **Files indexed:** ${stats.indexedFiles}\n`;
|
|
178
|
+
result += `- **Chunks created:** ${stats.totalChunks}\n`;
|
|
179
|
+
result += `- **Errors:** ${stats.errors}\n`;
|
|
180
|
+
result += `- **Duration:** ${stats.duration}ms\n`;
|
|
80
181
|
return result;
|
|
81
182
|
},
|
|
82
183
|
get_index_status: async (_args, ctx) => {
|
|
184
|
+
// Return cached result if still valid
|
|
185
|
+
if (_statusCache && Date.now() < _statusCache.expiresAt) {
|
|
186
|
+
const remainingMin = Math.round((_statusCache.expiresAt - Date.now()) / 60000);
|
|
187
|
+
return _statusCache.data + `\n_Cached (expires in ${remainingMin}min)_`;
|
|
188
|
+
}
|
|
83
189
|
const response = await ctx.api.get(`/api/index/status/${ctx.collectionPrefix}codebase`);
|
|
84
190
|
const data = response.data;
|
|
85
191
|
let result = `## Index Status: ${projectName}\n\n`;
|
|
@@ -88,21 +194,25 @@ export function createIndexingTools(projectName) {
|
|
|
88
194
|
result += `- **Indexed Files:** ${data.indexedFiles ?? "N/A"}\n`;
|
|
89
195
|
result += `- **Last Updated:** ${data.lastUpdated ? new Date(data.lastUpdated).toLocaleString() : "Never"}\n`;
|
|
90
196
|
result += `- **Vector Count:** ${data.vectorCount ?? "N/A"}\n`;
|
|
197
|
+
// Cache for 30 minutes
|
|
198
|
+
_statusCache = { data: result, expiresAt: Date.now() + STATUS_CACHE_TTL };
|
|
91
199
|
return result;
|
|
92
200
|
},
|
|
93
201
|
reindex_zero_downtime: async (args, ctx) => {
|
|
94
|
-
const { path, patterns, excludePatterns } = args;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
path: path || ctx.projectPath,
|
|
202
|
+
const { path: indexPath, patterns, excludePatterns } = args;
|
|
203
|
+
const projectPath = indexPath || ctx.projectPath;
|
|
204
|
+
const stats = await uploadFiles(ctx, projectPath, {
|
|
98
205
|
patterns,
|
|
99
206
|
excludePatterns,
|
|
207
|
+
force: true,
|
|
100
208
|
});
|
|
101
|
-
|
|
102
|
-
let result = `##
|
|
103
|
-
result += `- **
|
|
104
|
-
result += `- **
|
|
105
|
-
result += `- **
|
|
209
|
+
_statusCache = null; // Invalidate status cache after reindex
|
|
210
|
+
let result = `## Reindex: ${projectName}\n\n`;
|
|
211
|
+
result += `- **Total files found:** ${stats.totalFiles}\n`;
|
|
212
|
+
result += `- **Files indexed:** ${stats.indexedFiles}\n`;
|
|
213
|
+
result += `- **Chunks created:** ${stats.totalChunks}\n`;
|
|
214
|
+
result += `- **Errors:** ${stats.errors}\n`;
|
|
215
|
+
result += `- **Duration:** ${stats.duration}ms\n`;
|
|
106
216
|
return result;
|
|
107
217
|
},
|
|
108
218
|
list_aliases: async (_args, ctx) => {
|
|
@@ -2,12 +2,33 @@
|
|
|
2
2
|
* Suggestions tools module - contextual suggestions, related code,
|
|
3
3
|
* implementation suggestions, test suggestions, and code context.
|
|
4
4
|
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
5
7
|
import { truncate, pct, PREVIEW } from "../formatters.js";
|
|
6
8
|
/**
|
|
7
9
|
* Create the suggestions tools module with project-specific descriptions.
|
|
8
10
|
*/
|
|
9
11
|
export function createSuggestionTools(projectName) {
|
|
10
12
|
const tools = [
|
|
13
|
+
{
|
|
14
|
+
name: "context_briefing",
|
|
15
|
+
description: `REQUIRED before code changes. Parallel lookup of recall + search + patterns + ADRs + graph for ${projectName}. One call replaces 5 separate RAG lookups.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
task: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "What you will implement/change",
|
|
22
|
+
},
|
|
23
|
+
files: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: { type: "string" },
|
|
26
|
+
description: "Files you plan to modify",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["task"],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
11
32
|
{
|
|
12
33
|
name: "get_contextual_suggestions",
|
|
13
34
|
description: `Get contextual suggestions based on current work context for ${projectName}. Returns relevant suggestions, triggers, and related memories.`,
|
|
@@ -116,8 +137,153 @@ export function createSuggestionTools(projectName) {
|
|
|
116
137
|
},
|
|
117
138
|
},
|
|
118
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: "setup_project",
|
|
142
|
+
description: "Configure Claude Code for RAG integration. Creates/updates .mcp.json, adds RAG instructions to CLAUDE.md, and configures permissions. Call after index_codebase on a new project.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
projectPath: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Absolute path to project root",
|
|
149
|
+
},
|
|
150
|
+
projectName: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Project name in Qdrant (collection prefix)",
|
|
153
|
+
},
|
|
154
|
+
ragApiUrl: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "RAG API URL (default: from MCP env)",
|
|
157
|
+
},
|
|
158
|
+
ragApiKey: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "RAG API key (default: from MCP env)",
|
|
161
|
+
},
|
|
162
|
+
updateClaudeMd: {
|
|
163
|
+
type: "boolean",
|
|
164
|
+
description: "Add RAG section to CLAUDE.md (default: true)",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
required: ["projectPath", "projectName"],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
119
170
|
];
|
|
120
171
|
const handlers = {
|
|
172
|
+
context_briefing: async (args, ctx) => {
|
|
173
|
+
const { task, files } = args;
|
|
174
|
+
// 5 parallel lookups
|
|
175
|
+
const [memoriesRes, searchRes, patternsRes, adrsRes, graphRes] = await Promise.all([
|
|
176
|
+
// 1. Recall relevant memories
|
|
177
|
+
ctx.api
|
|
178
|
+
.post("/api/memory/recall", {
|
|
179
|
+
projectName: ctx.projectName,
|
|
180
|
+
query: task,
|
|
181
|
+
limit: 5,
|
|
182
|
+
type: "all",
|
|
183
|
+
})
|
|
184
|
+
.catch(() => null),
|
|
185
|
+
// 2. Hybrid search for related code
|
|
186
|
+
ctx.api
|
|
187
|
+
.post("/api/search-hybrid", {
|
|
188
|
+
projectName: ctx.projectName,
|
|
189
|
+
query: task,
|
|
190
|
+
limit: 5,
|
|
191
|
+
mode: "navigate",
|
|
192
|
+
})
|
|
193
|
+
.catch(() => null),
|
|
194
|
+
// 3. Architectural patterns
|
|
195
|
+
ctx.api
|
|
196
|
+
.post("/api/memory/recall", {
|
|
197
|
+
projectName: ctx.projectName,
|
|
198
|
+
query: task,
|
|
199
|
+
type: "context",
|
|
200
|
+
limit: 5,
|
|
201
|
+
tag: "pattern",
|
|
202
|
+
})
|
|
203
|
+
.catch(() => null),
|
|
204
|
+
// 4. ADRs
|
|
205
|
+
ctx.api
|
|
206
|
+
.post("/api/memory/recall", {
|
|
207
|
+
projectName: ctx.projectName,
|
|
208
|
+
query: task,
|
|
209
|
+
type: "decision",
|
|
210
|
+
limit: 3,
|
|
211
|
+
tag: "adr",
|
|
212
|
+
})
|
|
213
|
+
.catch(() => null),
|
|
214
|
+
// 5. Graph dependencies (if files specified)
|
|
215
|
+
files && files.length > 0
|
|
216
|
+
? ctx.api
|
|
217
|
+
.post("/api/search-graph", {
|
|
218
|
+
projectName: ctx.projectName,
|
|
219
|
+
query: files[0],
|
|
220
|
+
expandHops: 1,
|
|
221
|
+
limit: 5,
|
|
222
|
+
})
|
|
223
|
+
.catch(() => null)
|
|
224
|
+
: Promise.resolve(null),
|
|
225
|
+
]);
|
|
226
|
+
let result = `# Context Briefing: ${task}\n\n`;
|
|
227
|
+
// Memories
|
|
228
|
+
const memories = memoriesRes?.data?.results || memoriesRes?.data?.memories || [];
|
|
229
|
+
if (memories.length > 0) {
|
|
230
|
+
result += `## Memories (${memories.length})\n`;
|
|
231
|
+
for (const m of memories) {
|
|
232
|
+
const mem = m.memory || m;
|
|
233
|
+
result += `- [${mem.type || "note"}] ${truncate(mem.content || "", 150)}\n`;
|
|
234
|
+
}
|
|
235
|
+
result += "\n";
|
|
236
|
+
}
|
|
237
|
+
// Related code
|
|
238
|
+
const codeResults = searchRes?.data?.results || [];
|
|
239
|
+
if (codeResults.length > 0) {
|
|
240
|
+
result += `## Related Code (${codeResults.length})\n`;
|
|
241
|
+
for (const r of codeResults) {
|
|
242
|
+
result += `- \`${r.file}\``;
|
|
243
|
+
if (r.symbols?.length)
|
|
244
|
+
result += ` — ${r.symbols.join(", ")}`;
|
|
245
|
+
result += "\n";
|
|
246
|
+
}
|
|
247
|
+
result += "\n";
|
|
248
|
+
}
|
|
249
|
+
// Patterns
|
|
250
|
+
const patterns = (patternsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("pattern"));
|
|
251
|
+
if (patterns.length > 0) {
|
|
252
|
+
result += `## Patterns (${patterns.length})\n`;
|
|
253
|
+
for (const p of patterns) {
|
|
254
|
+
const name = p.memory?.metadata?.patternName || p.memory?.relatedTo || "Pattern";
|
|
255
|
+
result += `- **${name}**: ${truncate(p.memory?.content || "", 120)}\n`;
|
|
256
|
+
}
|
|
257
|
+
result += "\n";
|
|
258
|
+
}
|
|
259
|
+
// ADRs
|
|
260
|
+
const adrs = (adrsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("adr"));
|
|
261
|
+
if (adrs.length > 0) {
|
|
262
|
+
result += `## ADRs (${adrs.length})\n`;
|
|
263
|
+
for (const a of adrs) {
|
|
264
|
+
const title = a.memory?.metadata?.adrTitle || a.memory?.relatedTo || "ADR";
|
|
265
|
+
result += `- **${title}**: ${truncate(a.memory?.content || "", 120)}\n`;
|
|
266
|
+
}
|
|
267
|
+
result += "\n";
|
|
268
|
+
}
|
|
269
|
+
// Graph dependencies
|
|
270
|
+
const graphResults = graphRes?.data?.results || graphRes?.data?.directResults || [];
|
|
271
|
+
const connectedFiles = graphRes?.data?.connectedFiles || graphRes?.data?.expandedResults || [];
|
|
272
|
+
if (graphResults.length > 0 || connectedFiles.length > 0) {
|
|
273
|
+
result += `## Dependencies\n`;
|
|
274
|
+
for (const g of graphResults) {
|
|
275
|
+
result += `- \`${g.file}\`\n`;
|
|
276
|
+
}
|
|
277
|
+
for (const c of connectedFiles) {
|
|
278
|
+
result += `- \`${c.file}\` (connected)\n`;
|
|
279
|
+
}
|
|
280
|
+
result += "\n";
|
|
281
|
+
}
|
|
282
|
+
if (result.endsWith(`# Context Briefing: ${task}\n\n`)) {
|
|
283
|
+
result += "_No relevant context found. Proceed with implementation._\n";
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
},
|
|
121
287
|
get_contextual_suggestions: async (args, ctx) => {
|
|
122
288
|
const { currentFile, currentCode, recentFiles, task } = args;
|
|
123
289
|
const response = await ctx.api.post("/api/suggestions", {
|
|
@@ -296,6 +462,116 @@ export function createSuggestionTools(projectName) {
|
|
|
296
462
|
}
|
|
297
463
|
return result;
|
|
298
464
|
},
|
|
465
|
+
setup_project: async (args, ctx) => {
|
|
466
|
+
const { projectPath, projectName: targetProject, ragApiUrl, ragApiKey, updateClaudeMd = true, } = args;
|
|
467
|
+
const apiUrl = ragApiUrl || process.env.RAG_API_URL || "http://localhost:3100";
|
|
468
|
+
const apiKey = ragApiKey || process.env.RAG_API_KEY;
|
|
469
|
+
const serverName = `${targetProject}-rag`;
|
|
470
|
+
const changes = [];
|
|
471
|
+
// 1. Create/update .mcp.json
|
|
472
|
+
const mcpJsonPath = path.join(projectPath, ".mcp.json");
|
|
473
|
+
let mcpConfig = {};
|
|
474
|
+
try {
|
|
475
|
+
const existing = fs.readFileSync(mcpJsonPath, "utf-8");
|
|
476
|
+
mcpConfig = JSON.parse(existing);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// File doesn't exist or invalid JSON
|
|
480
|
+
}
|
|
481
|
+
if (!mcpConfig.mcpServers)
|
|
482
|
+
mcpConfig.mcpServers = {};
|
|
483
|
+
const serverEnv = {
|
|
484
|
+
RAG_API_URL: apiUrl,
|
|
485
|
+
PROJECT_NAME: targetProject,
|
|
486
|
+
PROJECT_PATH: projectPath,
|
|
487
|
+
};
|
|
488
|
+
if (apiKey)
|
|
489
|
+
serverEnv.RAG_API_KEY = apiKey;
|
|
490
|
+
mcpConfig.mcpServers[serverName] = {
|
|
491
|
+
command: "npx",
|
|
492
|
+
args: ["-y", "@crowley/rag-mcp@latest"],
|
|
493
|
+
env: serverEnv,
|
|
494
|
+
};
|
|
495
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
496
|
+
changes.push(`.mcp.json — added \`${serverName}\` server`);
|
|
497
|
+
// 2. Update CLAUDE.md with RAG section
|
|
498
|
+
if (updateClaudeMd) {
|
|
499
|
+
const claudeMdPath = path.join(projectPath, "CLAUDE.md");
|
|
500
|
+
let claudeMd = "";
|
|
501
|
+
try {
|
|
502
|
+
claudeMd = fs.readFileSync(claudeMdPath, "utf-8");
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// File doesn't exist
|
|
506
|
+
}
|
|
507
|
+
const ragSection = `\n## RAG Integration
|
|
508
|
+
|
|
509
|
+
You MUST call \`context_briefing\` before making any code changes.
|
|
510
|
+
This single tool performs all RAG lookups in parallel (recall, search, patterns, ADRs, graph).
|
|
511
|
+
|
|
512
|
+
Example: \`context_briefing(task: "describe your change", files: ["path/to/file.ts"])\`
|
|
513
|
+
|
|
514
|
+
After completing significant changes:
|
|
515
|
+
- \`remember\` — save important context for future sessions
|
|
516
|
+
- \`record_adr\` — document architectural decisions
|
|
517
|
+
`;
|
|
518
|
+
if (claudeMd.includes("## RAG")) {
|
|
519
|
+
changes.push("CLAUDE.md — RAG section already exists, skipped");
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
claudeMd = claudeMd ? claudeMd.trimEnd() + "\n" + ragSection : `# CLAUDE.md\n${ragSection}`;
|
|
523
|
+
fs.writeFileSync(claudeMdPath, claudeMd);
|
|
524
|
+
changes.push("CLAUDE.md — added RAG Integration section");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// 3. Create/update .claude/settings.local.json permissions
|
|
528
|
+
const claudeDir = path.join(projectPath, ".claude");
|
|
529
|
+
const settingsPath = path.join(claudeDir, "settings.local.json");
|
|
530
|
+
let settings = {};
|
|
531
|
+
try {
|
|
532
|
+
const existing = fs.readFileSync(settingsPath, "utf-8");
|
|
533
|
+
settings = JSON.parse(existing);
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// File doesn't exist or invalid JSON
|
|
537
|
+
}
|
|
538
|
+
if (!settings.permissions)
|
|
539
|
+
settings.permissions = {};
|
|
540
|
+
if (!settings.permissions.allow)
|
|
541
|
+
settings.permissions.allow = [];
|
|
542
|
+
const mcpPermission = `mcp__${serverName}__*`;
|
|
543
|
+
if (!settings.permissions.allow.includes(mcpPermission)) {
|
|
544
|
+
settings.permissions.allow.push(mcpPermission);
|
|
545
|
+
if (!fs.existsSync(claudeDir))
|
|
546
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
547
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
548
|
+
changes.push(`.claude/settings.local.json — added \`${mcpPermission}\` permission`);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
changes.push(".claude/settings.local.json — permission already exists, skipped");
|
|
552
|
+
}
|
|
553
|
+
// 4. Check index status
|
|
554
|
+
let indexInfo = "";
|
|
555
|
+
try {
|
|
556
|
+
const statusRes = await ctx.api.get(`/api/index/status/${targetProject}_codebase`);
|
|
557
|
+
const data = statusRes.data;
|
|
558
|
+
indexInfo = `\n## Index Status\n- **Vectors:** ${data.vectorCount ?? "N/A"}\n- **Status:** ${data.status || "unknown"}\n`;
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
indexInfo = "\n## Index Status\n_Not indexed yet. Run `index_codebase` first._\n";
|
|
562
|
+
}
|
|
563
|
+
let result = `# Project Setup: ${targetProject}\n\n`;
|
|
564
|
+
result += `## Files Updated\n`;
|
|
565
|
+
for (const c of changes) {
|
|
566
|
+
result += `- ${c}\n`;
|
|
567
|
+
}
|
|
568
|
+
result += indexInfo;
|
|
569
|
+
result += `\n## Next Steps\n`;
|
|
570
|
+
result += `1. Restart Claude Code to load the new MCP server\n`;
|
|
571
|
+
result += `2. Run \`index_codebase\` if not indexed yet\n`;
|
|
572
|
+
result += `3. Use \`context_briefing\` before code changes\n`;
|
|
573
|
+
return result;
|
|
574
|
+
},
|
|
299
575
|
};
|
|
300
576
|
return { tools, handlers };
|
|
301
577
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowley/rag-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
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",
|