@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.
@@ -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, _args: Record<string, unknown>, _result: string, ctx: ToolContext): void;
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, _args, _result, ctx) {
85
- if (!ctx.activeSessionId)
86
- return;
87
- // Fire-and-forget: track tool usage in session
88
- ctx.api
89
- .post(`/api/session/${ctx.activeSessionId}/activity`, {
90
- projectName: ctx.projectName,
91
- type: "tool",
92
- value: name,
93
- })
94
- .catch(() => {
95
- // Silently ignore tracking errors
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", {
@@ -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
  /**
@@ -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 response = await ctx.api.post("/api/index", {
72
- collection: `${ctx.collectionPrefix}codebase`,
73
- path: path || ctx.projectPath,
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 += `- **Status:** ${data.status || "started"}\n`;
79
- result += `- **Files to process:** ${data.filesToProcess ?? "N/A"}\n`;
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 response = await ctx.api.post("/api/reindex", {
96
- collection: `${ctx.collectionPrefix}codebase`,
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
- const data = response.data;
102
- let result = `## Zero-Downtime Reindex: ${projectName}\n\n`;
103
- result += `- **Alias:** ${data.alias || "N/A"}\n`;
104
- result += `- **Status:** ${data.status || "started"}\n`;
105
- result += `- **Message:** ${data.message || "Reindex initiated"}\n`;
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",
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",