@crowley/rag-mcp 1.5.0 → 1.6.1

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.
@@ -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, formatNavigationResults, truncate } from "../formatters.js";
5
+ import { formatCodeResults, formatNavigationResults, truncate, } from "../formatters.js";
6
6
  import { z } from "zod";
7
7
  import { TOOL_ANNOTATIONS } from "../annotations.js";
8
8
  /**
@@ -15,15 +15,30 @@ export function createSearchTools(projectName) {
15
15
  description: `Search the ${projectName} codebase. Returns file locations, symbols, and graph connections. Use Read tool to view the actual code at returned locations.`,
16
16
  schema: z.object({
17
17
  query: z.string().describe("Search query for finding code"),
18
- limit: z.coerce.number().optional().describe("Max results to return (default: 5)"),
19
- language: z.string().optional().describe("Filter by language (typescript, python, vue, etc.)"),
20
- path: z.string().optional().describe("Filter by path pattern (e.g., 'src/modules/*')"),
21
- layer: z.string().optional().describe("Filter by architectural layer (api, service, util, model, middleware, test, parser, types, config, other)"),
22
- service: z.string().optional().describe("Filter by service/class name (e.g., 'EmbeddingService')"),
18
+ limit: z.coerce
19
+ .number()
20
+ .optional()
21
+ .describe("Max results to return (default: 5)"),
22
+ language: z
23
+ .string()
24
+ .optional()
25
+ .describe("Filter by language (typescript, python, vue, etc.)"),
26
+ path: z
27
+ .string()
28
+ .optional()
29
+ .describe("Filter by path pattern (e.g., 'src/modules/*')"),
30
+ layer: z
31
+ .string()
32
+ .optional()
33
+ .describe("Filter by architectural layer (api, service, util, model, middleware, test, parser, types, config, other)"),
34
+ service: z
35
+ .string()
36
+ .optional()
37
+ .describe("Filter by service/class name (e.g., 'EmbeddingService')"),
23
38
  }),
24
39
  annotations: TOOL_ANNOTATIONS["search_codebase"],
25
40
  handler: async (args, ctx) => {
26
- const { query, limit = 5, language, path, layer, service } = args;
41
+ const { query, limit = 5, language, path, layer, service, } = args;
27
42
  const response = await ctx.api.post("/api/search", {
28
43
  collection: `${ctx.collectionPrefix}codebase`,
29
44
  query,
@@ -43,7 +58,10 @@ export function createSearchTools(projectName) {
43
58
  description: "Find code similar to a given snippet.",
44
59
  schema: z.object({
45
60
  code: z.string().describe("Code snippet to find similar code for"),
46
- limit: z.coerce.number().optional().describe("Max results (default: 5)"),
61
+ limit: z.coerce
62
+ .number()
63
+ .optional()
64
+ .describe("Max results (default: 5)"),
47
65
  }),
48
66
  annotations: TOOL_ANNOTATIONS["search_similar"],
49
67
  handler: async (args, ctx) => {
@@ -65,17 +83,30 @@ export function createSearchTools(projectName) {
65
83
  description: `Search ${projectName} codebase grouped by file. Returns file locations with symbols and connections. Use Read tool to view the actual code.`,
66
84
  schema: z.object({
67
85
  query: z.string().describe("Search query"),
68
- groupBy: z.string().optional().describe("Field to group by (default: 'file')"),
69
- limit: z.coerce.number().optional().describe("Max groups to return (default: 10)"),
86
+ groupBy: z
87
+ .string()
88
+ .optional()
89
+ .describe("Field to group by (default: 'file')"),
90
+ limit: z.coerce
91
+ .number()
92
+ .optional()
93
+ .describe("Max groups to return (default: 10)"),
70
94
  language: z.string().optional().describe("Filter by language"),
71
- layer: z.string().optional().describe("Filter by architectural layer (api, service, util, etc.)"),
95
+ layer: z
96
+ .string()
97
+ .optional()
98
+ .describe("Filter by architectural layer (api, service, util, etc.)"),
72
99
  service: z.string().optional().describe("Filter by service/class name"),
73
100
  }),
74
101
  annotations: TOOL_ANNOTATIONS["grouped_search"],
75
102
  handler: async (args, ctx) => {
76
- const { query, groupBy = "file", limit = 10, language, layer, service } = args;
77
- const filters = { language, layer, service };
78
- const hasFilters = Object.values(filters).some(v => v !== undefined);
103
+ const { query, groupBy = "file", limit = 10, language, layer, service, } = args;
104
+ const filters = {
105
+ language,
106
+ layer,
107
+ service,
108
+ };
109
+ const hasFilters = Object.values(filters).some((v) => v !== undefined);
79
110
  const response = await ctx.api.post("/api/search-grouped", {
80
111
  collection: `${ctx.collectionPrefix}codebase`,
81
112
  query,
@@ -97,17 +128,30 @@ export function createSearchTools(projectName) {
97
128
  description: `Hybrid search combining keyword matching and semantic similarity for ${projectName}. Returns file locations with symbols and connections. Use Read tool to view code.`,
98
129
  schema: z.object({
99
130
  query: z.string().describe("Search query"),
100
- limit: z.coerce.number().optional().describe("Max results (default: 10)"),
101
- semanticWeight: z.coerce.number().optional().describe("Weight for semantic vs keyword (0-1, default: 0.7)"),
131
+ limit: z.coerce
132
+ .number()
133
+ .optional()
134
+ .describe("Max results (default: 10)"),
135
+ semanticWeight: z.coerce
136
+ .number()
137
+ .optional()
138
+ .describe("Weight for semantic vs keyword (0-1, default: 0.7)"),
102
139
  language: z.string().optional().describe("Filter by language"),
103
- layer: z.string().optional().describe("Filter by architectural layer (api, service, util, etc.)"),
140
+ layer: z
141
+ .string()
142
+ .optional()
143
+ .describe("Filter by architectural layer (api, service, util, etc.)"),
104
144
  service: z.string().optional().describe("Filter by service/class name"),
105
145
  }),
106
146
  annotations: TOOL_ANNOTATIONS["hybrid_search"],
107
147
  handler: async (args, ctx) => {
108
- const { query, limit = 10, semanticWeight = 0.7, language, layer, service } = args;
109
- const filters = { language, layer, service };
110
- const hasFilters = Object.values(filters).some(v => v !== undefined);
148
+ const { query, limit = 10, semanticWeight = 0.7, language, layer, service, } = args;
149
+ const filters = {
150
+ language,
151
+ layer,
152
+ service,
153
+ };
154
+ const hasFilters = Object.values(filters).some((v) => v !== undefined);
111
155
  const response = await ctx.api.post("/api/search-hybrid", {
112
156
  collection: `${ctx.collectionPrefix}codebase`,
113
157
  query,
@@ -128,7 +172,10 @@ export function createSearchTools(projectName) {
128
172
  description: `Search documentation in the ${projectName} project.`,
129
173
  schema: z.object({
130
174
  query: z.string().describe("Search query"),
131
- limit: z.coerce.number().optional().describe("Max results (default: 5)"),
175
+ limit: z.coerce
176
+ .number()
177
+ .optional()
178
+ .describe("Max results (default: 5)"),
132
179
  }),
133
180
  annotations: TOOL_ANNOTATIONS["search_docs"],
134
181
  handler: async (args, ctx) => {
@@ -143,8 +190,7 @@ export function createSearchTools(projectName) {
143
190
  return "No documentation found for this query.";
144
191
  }
145
192
  return results
146
- .map((r) => `**${r.file}**\n` +
147
- truncate(r.content, 500))
193
+ .map((r) => `**${r.file}**\n` + truncate(r.content, 500))
148
194
  .join("\n\n---\n\n");
149
195
  },
150
196
  },
@@ -192,9 +238,17 @@ export function createSearchTools(projectName) {
192
238
  name: "find_symbol",
193
239
  description: `Find a function, class, type, or interface by name in ${projectName}. Fast symbol lookup without full-text search.`,
194
240
  schema: z.object({
195
- symbol: z.string().describe("Symbol name to find (function, class, type, etc.)"),
196
- kind: z.string().optional().describe("Filter by kind: function, class, interface, type, enum, const"),
197
- limit: z.coerce.number().optional().describe("Max results (default: 10)"),
241
+ symbol: z
242
+ .string()
243
+ .describe("Symbol name to find (function, class, type, etc.)"),
244
+ kind: z
245
+ .string()
246
+ .optional()
247
+ .describe("Filter by kind: function, class, interface, type, enum, const"),
248
+ limit: z.coerce
249
+ .number()
250
+ .optional()
251
+ .describe("Max results (default: 10)"),
198
252
  }),
199
253
  outputSchema: z.object({
200
254
  symbols: z.array(z.object({
@@ -209,7 +263,7 @@ export function createSearchTools(projectName) {
209
263
  }),
210
264
  annotations: TOOL_ANNOTATIONS["find_symbol"],
211
265
  handler: async (args, ctx) => {
212
- const { symbol, kind, limit = 10 } = args;
266
+ const { symbol, kind, limit = 10, } = args;
213
267
  const response = await ctx.api.post("/api/find-symbol", {
214
268
  projectName: ctx.projectName,
215
269
  symbol,
@@ -246,12 +300,18 @@ export function createSearchTools(projectName) {
246
300
  description: `Search ${projectName} codebase with graph expansion. Returns file locations plus connected files via import/call relationships. Use Read tool to view code.`,
247
301
  schema: z.object({
248
302
  query: z.string().describe("Search query"),
249
- limit: z.coerce.number().optional().describe("Max direct results (default: 5)"),
250
- expandHops: z.coerce.number().optional().describe("Number of graph hops to expand (default: 1)"),
303
+ limit: z.coerce
304
+ .number()
305
+ .optional()
306
+ .describe("Max direct results (default: 5)"),
307
+ expandHops: z.coerce
308
+ .number()
309
+ .optional()
310
+ .describe("Number of graph hops to expand (default: 1)"),
251
311
  }),
252
312
  annotations: TOOL_ANNOTATIONS["search_graph"],
253
313
  handler: async (args, ctx) => {
254
- const { query, limit = 5, expandHops = 1 } = args;
314
+ const { query, limit = 5, expandHops = 1, } = args;
255
315
  const response = await ctx.api.post("/api/search-graph", {
256
316
  collection: `${ctx.collectionPrefix}codebase`,
257
317
  query,
@@ -260,7 +320,8 @@ export function createSearchTools(projectName) {
260
320
  mode: "navigate",
261
321
  });
262
322
  const { results, graphExpanded, expandedFiles } = response.data;
263
- if ((!results || results.length === 0) && (!graphExpanded || graphExpanded.length === 0)) {
323
+ if ((!results || results.length === 0) &&
324
+ (!graphExpanded || graphExpanded.length === 0)) {
264
325
  return "No results found.";
265
326
  }
266
327
  let output = "";
@@ -15,7 +15,10 @@ export function createSessionTools(projectName, sharedCtx) {
15
15
  name: "summarize_context",
16
16
  description: `Summarize the current working context for ${projectName}. Shows recently used tools, active features, recent queries, and suggested next steps.`,
17
17
  schema: z.object({
18
- sessionId: z.string().optional().describe("Session ID to get context for. If omitted, returns the latest context."),
18
+ sessionId: z
19
+ .string()
20
+ .optional()
21
+ .describe("Session ID to get context for. If omitted, returns the latest context."),
19
22
  }),
20
23
  annotations: TOOL_ANNOTATIONS["summarize_context"],
21
24
  handler: async (args, ctx) => {
@@ -26,16 +29,12 @@ export function createSessionTools(projectName, sharedCtx) {
26
29
  let result = `**Context Summary for ${ctx.projectName}**\n\n`;
27
30
  if (data.recentTools && data.recentTools.length > 0) {
28
31
  result += `**Recently Used Tools:**\n`;
29
- result += data.recentTools
30
- .map((t) => `- ${t}`)
31
- .join("\n");
32
+ result += data.recentTools.map((t) => `- ${t}`).join("\n");
32
33
  result += "\n\n";
33
34
  }
34
35
  if (data.activeFeatures && data.activeFeatures.length > 0) {
35
36
  result += `**Active Features:**\n`;
36
- result += data.activeFeatures
37
- .map((f) => `- ${f}`)
38
- .join("\n");
37
+ result += data.activeFeatures.map((f) => `- ${f}`).join("\n");
39
38
  result += "\n\n";
40
39
  }
41
40
  if (data.recentQueries && data.recentQueries.length > 0) {
@@ -61,7 +60,10 @@ export function createSessionTools(projectName, sharedCtx) {
61
60
  description: `Summarize changes made during a session for ${projectName}. Shows what was modified, tools used, and key actions taken.`,
62
61
  schema: z.object({
63
62
  sessionId: z.string().describe("Session ID to summarize changes for."),
64
- includeCode: z.boolean().optional().describe("Whether to include code snippets in the summary (default: false)."),
63
+ includeCode: z
64
+ .boolean()
65
+ .optional()
66
+ .describe("Whether to include code snippets in the summary (default: false)."),
65
67
  }),
66
68
  annotations: TOOL_ANNOTATIONS["summarize_changes"],
67
69
  handler: async (args, ctx) => {
@@ -102,7 +104,10 @@ export function createSessionTools(projectName, sharedCtx) {
102
104
  name: "analyze_usage_patterns",
103
105
  description: `Analyze tool usage patterns for ${projectName}. Shows common workflows, detected patterns, and recommendations for improving productivity.`,
104
106
  schema: z.object({
105
- days: z.coerce.number().optional().describe("Number of days to analyze (default: 7)."),
107
+ days: z.coerce
108
+ .number()
109
+ .optional()
110
+ .describe("Number of days to analyze (default: 7)."),
106
111
  }),
107
112
  annotations: TOOL_ANNOTATIONS["analyze_usage_patterns"],
108
113
  handler: async (args, ctx) => {
@@ -147,7 +152,9 @@ export function createSessionTools(projectName, sharedCtx) {
147
152
  schema: z.object({}),
148
153
  annotations: TOOL_ANNOTATIONS["get_developer_profile"],
149
154
  handler: async (_args, ctx) => {
150
- const response = await ctx.api.get(`/api/developer-profile`, { headers: { "X-Project-Name": ctx.projectName } });
155
+ const response = await ctx.api.get(`/api/developer-profile`, {
156
+ headers: { "X-Project-Name": ctx.projectName },
157
+ });
151
158
  const p = response.data;
152
159
  if (!p.totalToolCalls) {
153
160
  return "No usage data yet. Use tools to build your developer profile.";
@@ -155,22 +162,33 @@ export function createSessionTools(projectName, sharedCtx) {
155
162
  let result = `**Developer Profile** (${p.totalSessions} sessions, ${p.totalToolCalls} tool calls)\n\n`;
156
163
  if (p.frequentFiles.length > 0) {
157
164
  result += "**Frequent Files:**\n";
158
- result += p.frequentFiles.slice(0, 10).map((f) => `- ${f.file} (${f.count}x)`).join("\n");
165
+ result += p.frequentFiles
166
+ .slice(0, 10)
167
+ .map((f) => `- ${f.file} (${f.count}x)`)
168
+ .join("\n");
159
169
  result += "\n\n";
160
170
  }
161
171
  if (p.preferredTools.length > 0) {
162
172
  result += "**Preferred Tools:**\n";
163
- result += p.preferredTools.slice(0, 8).map((t) => `- ${t.tool}: ${t.count}x (avg ${Math.round(t.avgDurationMs)}ms)`).join("\n");
173
+ result += p.preferredTools
174
+ .slice(0, 8)
175
+ .map((t) => `- ${t.tool}: ${t.count}x (avg ${Math.round(t.avgDurationMs)}ms)`)
176
+ .join("\n");
164
177
  result += "\n\n";
165
178
  }
166
179
  if (p.peakHours.length > 0) {
167
180
  result += "**Peak Hours:** ";
168
- result += p.peakHours.map((h) => `${h.hour}:00 (${h.count})`).join(", ");
181
+ result += p.peakHours
182
+ .map((h) => `${h.hour}:00 (${h.count})`)
183
+ .join(", ");
169
184
  result += "\n\n";
170
185
  }
171
186
  if (p.commonPatterns.length > 0) {
172
187
  result += "**Common Patterns:**\n";
173
- result += p.commonPatterns.slice(0, 5).map((q) => `- "${truncate(q, 60)}"`).join("\n");
188
+ result += p.commonPatterns
189
+ .slice(0, 5)
190
+ .map((q) => `- "${truncate(q, 60)}"`)
191
+ .join("\n");
174
192
  result += "\n";
175
193
  }
176
194
  return result;
@@ -180,9 +198,18 @@ export function createSessionTools(projectName, sharedCtx) {
180
198
  name: "start_session",
181
199
  description: `Start a new working session for ${projectName}. Tracks tool usage, file changes, and learnings throughout the session.`,
182
200
  schema: z.object({
183
- sessionId: z.string().optional().describe("Custom session ID. If omitted, one will be generated."),
184
- initialContext: z.string().optional().describe("Description of what this session is about (e.g., 'fixing auth bug', 'adding new API endpoint')."),
185
- resumeFrom: z.string().optional().describe("Session ID to resume from. Carries over context from the previous session."),
201
+ sessionId: z
202
+ .string()
203
+ .optional()
204
+ .describe("Custom session ID. If omitted, one will be generated."),
205
+ initialContext: z
206
+ .string()
207
+ .optional()
208
+ .describe("Description of what this session is about (e.g., 'fixing auth bug', 'adding new API endpoint')."),
209
+ resumeFrom: z
210
+ .string()
211
+ .optional()
212
+ .describe("Session ID to resume from. Carries over context from the previous session."),
186
213
  }),
187
214
  annotations: TOOL_ANNOTATIONS["start_session"],
188
215
  handler: async (args, ctx) => {
@@ -212,9 +239,7 @@ export function createSessionTools(projectName, sharedCtx) {
212
239
  }
213
240
  if (initialFiles && initialFiles.length > 0) {
214
241
  result += `\n**Initial Files:**\n`;
215
- result += initialFiles
216
- .map((f) => `- ${f}`)
217
- .join("\n");
242
+ result += initialFiles.map((f) => `- ${f}`).join("\n");
218
243
  result += "\n";
219
244
  }
220
245
  // Include prefetch stats if available
@@ -259,9 +284,7 @@ export function createSessionTools(projectName, sharedCtx) {
259
284
  }
260
285
  if (data.activeFeatures && data.activeFeatures.length > 0) {
261
286
  result += `\n**Active Features:**\n`;
262
- result += data.activeFeatures
263
- .map((f) => `- ${f}`)
264
- .join("\n");
287
+ result += data.activeFeatures.map((f) => `- ${f}`).join("\n");
265
288
  result += "\n";
266
289
  }
267
290
  if (data.pendingLearnings && data.pendingLearnings.length > 0) {
@@ -283,9 +306,18 @@ export function createSessionTools(projectName, sharedCtx) {
283
306
  description: `End a working session for ${projectName}. Saves a summary and optionally extracts learnings for future sessions.`,
284
307
  schema: z.object({
285
308
  sessionId: z.string().describe("Session ID to end."),
286
- summary: z.string().optional().describe("Summary of what was accomplished during the session."),
287
- autoSaveLearnings: z.boolean().optional().describe("Automatically save detected learnings to memory (default: true)."),
288
- feedback: z.string().optional().describe("Optional feedback about the session (e.g., 'productive', 'too many context switches')."),
309
+ summary: z
310
+ .string()
311
+ .optional()
312
+ .describe("Summary of what was accomplished during the session."),
313
+ autoSaveLearnings: z
314
+ .boolean()
315
+ .optional()
316
+ .describe("Automatically save detected learnings to memory (default: true)."),
317
+ feedback: z
318
+ .string()
319
+ .optional()
320
+ .describe("Optional feedback about the session (e.g., 'productive', 'too many context switches')."),
289
321
  }),
290
322
  annotations: TOOL_ANNOTATIONS["end_session"],
291
323
  handler: async (args, ctx) => {
@@ -86,7 +86,10 @@ export function createSuggestionTools(projectName) {
86
86
  description: `REQUIRED before code changes. Parallel lookup of recall + search + patterns + ADRs + graph for ${projectName}. One call replaces 5 separate RAG lookups.`,
87
87
  schema: z.object({
88
88
  task: z.string().describe("What you will implement/change"),
89
- files: z.array(z.string()).optional().describe("Files you plan to modify"),
89
+ files: z
90
+ .array(z.string())
91
+ .optional()
92
+ .describe("Files you plan to modify"),
90
93
  }),
91
94
  annotations: TOOL_ANNOTATIONS["context_briefing"],
92
95
  handler: async (args, ctx) => {
@@ -176,7 +179,9 @@ export function createSuggestionTools(projectName) {
176
179
  if (patterns.length > 0) {
177
180
  result += `## Patterns (${patterns.length})\n`;
178
181
  for (const p of patterns) {
179
- const name = p.memory?.metadata?.patternName || p.memory?.relatedTo || "Pattern";
182
+ const name = p.memory?.metadata?.patternName ||
183
+ p.memory?.relatedTo ||
184
+ "Pattern";
180
185
  result += `- **${name}**: ${truncate(p.memory?.content || "", 120)}\n`;
181
186
  }
182
187
  result += "\n";
@@ -191,7 +196,9 @@ export function createSuggestionTools(projectName) {
191
196
  result += "\n";
192
197
  }
193
198
  const graphResults = graphRes?.data?.results || graphRes?.data?.directResults || [];
194
- const connectedFiles = graphRes?.data?.connectedFiles || graphRes?.data?.expandedResults || [];
199
+ const connectedFiles = graphRes?.data?.connectedFiles ||
200
+ graphRes?.data?.expandedResults ||
201
+ [];
195
202
  if (graphResults.length > 0 || connectedFiles.length > 0) {
196
203
  result += `## Dependencies\n`;
197
204
  for (const g of graphResults) {
@@ -203,7 +210,8 @@ export function createSuggestionTools(projectName) {
203
210
  result += "\n";
204
211
  }
205
212
  if (result.endsWith(`# Context Briefing: ${task}\n\n`)) {
206
- result += "_No relevant context found. Proceed with implementation._\n";
213
+ result +=
214
+ "_No relevant context found. Proceed with implementation._\n";
207
215
  }
208
216
  return result;
209
217
  },
@@ -213,8 +221,14 @@ export function createSuggestionTools(projectName) {
213
221
  description: `Intelligent task routing for ${projectName}. LLM analyzes your task and runs only the needed lookups (2-5 of 7 available) in parallel. More efficient than context_briefing for narrow tasks.`,
214
222
  schema: z.object({
215
223
  task: z.string().describe("What you will implement/change"),
216
- files: z.array(z.string()).optional().describe("Files you plan to modify"),
217
- intent: z.enum(["code", "research", "debug", "review", "architecture"]).optional().describe("Task intent for better routing"),
224
+ files: z
225
+ .array(z.string())
226
+ .optional()
227
+ .describe("Files you plan to modify"),
228
+ intent: z
229
+ .enum(["code", "research", "debug", "review", "architecture"])
230
+ .optional()
231
+ .describe("Task intent for better routing"),
218
232
  }),
219
233
  annotations: TOOL_ANNOTATIONS["context_briefing"], // Same annotations as context_briefing
220
234
  handler: async (args, ctx) => {
@@ -232,9 +246,18 @@ export function createSuggestionTools(projectName) {
232
246
  name: "get_contextual_suggestions",
233
247
  description: `Get contextual suggestions based on current work context for ${projectName}. Returns relevant suggestions, triggers, and related memories.`,
234
248
  schema: z.object({
235
- currentFile: z.string().optional().describe("Currently active file path"),
236
- currentCode: z.string().optional().describe("Currently selected or visible code"),
237
- recentFiles: z.array(z.string()).optional().describe("Recently opened file paths"),
249
+ currentFile: z
250
+ .string()
251
+ .optional()
252
+ .describe("Currently active file path"),
253
+ currentCode: z
254
+ .string()
255
+ .optional()
256
+ .describe("Currently selected or visible code"),
257
+ recentFiles: z
258
+ .array(z.string())
259
+ .optional()
260
+ .describe("Recently opened file paths"),
238
261
  task: z.string().optional().describe("Current task description"),
239
262
  }),
240
263
  annotations: TOOL_ANNOTATIONS["get_contextual_suggestions"],
@@ -288,13 +311,22 @@ export function createSuggestionTools(projectName) {
288
311
  name: "suggest_related_code",
289
312
  description: `Find code related to a given file or snippet in ${projectName}. Shows similar implementations and related modules.`,
290
313
  schema: z.object({
291
- file: z.string().optional().describe("File path to find related code for"),
292
- code: z.string().optional().describe("Code snippet to find related code for"),
293
- limit: z.coerce.number().optional().describe("Max results (default: 5)"),
314
+ file: z
315
+ .string()
316
+ .optional()
317
+ .describe("File path to find related code for"),
318
+ code: z
319
+ .string()
320
+ .optional()
321
+ .describe("Code snippet to find related code for"),
322
+ limit: z.coerce
323
+ .number()
324
+ .optional()
325
+ .describe("Max results (default: 5)"),
294
326
  }),
295
327
  annotations: TOOL_ANNOTATIONS["suggest_related_code"],
296
328
  handler: async (args, ctx) => {
297
- const { file, code, limit = 5 } = args;
329
+ const { file, code, limit = 5, } = args;
298
330
  const response = await ctx.api.post("/api/code/related", {
299
331
  projectName: ctx.projectName,
300
332
  file,
@@ -315,7 +347,10 @@ export function createSuggestionTools(projectName) {
315
347
  result += ` | Line ${r.line}`;
316
348
  result += "\n";
317
349
  if (r.content || r.code) {
318
- result += "```\n" + truncate(r.content || r.code, PREVIEW.MEDIUM) + "\n```\n";
350
+ result +=
351
+ "```\n" +
352
+ truncate(r.content || r.code, PREVIEW.MEDIUM) +
353
+ "\n```\n";
319
354
  }
320
355
  result += "\n";
321
356
  }
@@ -371,7 +406,10 @@ export function createSuggestionTools(projectName) {
371
406
  schema: z.object({
372
407
  file: z.string().optional().describe("File to suggest tests for"),
373
408
  code: z.string().optional().describe("Code to suggest tests for"),
374
- framework: z.string().optional().describe("Test framework preference (jest, mocha, pytest, etc.)"),
409
+ framework: z
410
+ .string()
411
+ .optional()
412
+ .describe("Test framework preference (jest, mocha, pytest, etc.)"),
375
413
  }),
376
414
  annotations: TOOL_ANNOTATIONS["suggest_tests"],
377
415
  handler: async (args, ctx) => {
@@ -401,7 +439,8 @@ export function createSuggestionTools(projectName) {
401
439
  if (t.coverage)
402
440
  result += `**Coverage:** ${t.coverage}\n`;
403
441
  if (t.content || t.code) {
404
- result += "```\n" + truncate(t.content || t.code, PREVIEW.LONG) + "\n```\n";
442
+ result +=
443
+ "```\n" + truncate(t.content || t.code, PREVIEW.LONG) + "\n```\n";
405
444
  }
406
445
  result += "\n";
407
446
  }
@@ -461,10 +500,21 @@ export function createSuggestionTools(projectName) {
461
500
  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.",
462
501
  schema: z.object({
463
502
  projectPath: z.string().describe("Absolute path to project root"),
464
- projectName: z.string().describe("Project name in Qdrant (collection prefix)"),
465
- ragApiUrl: z.string().optional().describe("RAG API URL (default: from MCP env)"),
466
- ragApiKey: z.string().optional().describe("RAG API key (default: from MCP env)"),
467
- updateClaudeMd: z.boolean().optional().describe("Add RAG section to CLAUDE.md (default: true)"),
503
+ projectName: z
504
+ .string()
505
+ .describe("Project name in Qdrant (collection prefix)"),
506
+ ragApiUrl: z
507
+ .string()
508
+ .optional()
509
+ .describe("RAG API URL (default: from MCP env)"),
510
+ ragApiKey: z
511
+ .string()
512
+ .optional()
513
+ .describe("RAG API key (default: from MCP env)"),
514
+ updateClaudeMd: z
515
+ .boolean()
516
+ .optional()
517
+ .describe("Add RAG section to CLAUDE.md (default: true)"),
468
518
  }),
469
519
  annotations: TOOL_ANNOTATIONS["setup_project"],
470
520
  handler: async (args, ctx) => {
@@ -524,7 +574,9 @@ After completing significant changes:
524
574
  changes.push("CLAUDE.md — RAG section already exists, skipped");
525
575
  }
526
576
  else {
527
- claudeMd = claudeMd ? claudeMd.trimEnd() + "\n" + ragSection : `# CLAUDE.md\n${ragSection}`;
577
+ claudeMd = claudeMd
578
+ ? claudeMd.trimEnd() + "\n" + ragSection
579
+ : `# CLAUDE.md\n${ragSection}`;
528
580
  fs.writeFileSync(claudeMdPath, claudeMd);
529
581
  changes.push("CLAUDE.md — added RAG Integration section");
530
582
  }
@@ -563,7 +615,8 @@ After completing significant changes:
563
615
  indexInfo = `\n## Index Status\n- **Vectors:** ${data.vectorCount ?? "N/A"}\n- **Status:** ${data.status || "unknown"}\n`;
564
616
  }
565
617
  catch {
566
- indexInfo = "\n## Index Status\n_Not indexed yet. Run `index_codebase` first._\n";
618
+ indexInfo =
619
+ "\n## Index Status\n_Not indexed yet. Run `index_codebase` first._\n";
567
620
  }
568
621
  let result = `# Project Setup: ${targetProject}\n\n`;
569
622
  result += `## Files Updated\n`;
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared types for the MCP server tool modules.
3
3
  */
4
- import type { AxiosInstance } from "axios";
4
+ import type { ApiClient } from "./api-client.js";
5
5
  import type { z } from "zod";
6
6
  import type { ToolAnnotations } from "./annotations.js";
7
7
  /** MCP tool input schema shape (raw JSON Schema) */
@@ -19,7 +19,7 @@ export interface ToolDefinition {
19
19
  }
20
20
  /** Context passed to every tool handler */
21
21
  export interface ToolContext {
22
- api: AxiosInstance;
22
+ api: ApiClient;
23
23
  projectName: string;
24
24
  projectPath: string;
25
25
  collectionPrefix: string;