@crowley/rag-mcp 1.0.1 → 1.0.3
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/tool-registry.d.ts +8 -0
- package/dist/tool-registry.js +54 -0
- package/dist/tools/search.js +18 -38
- package/dist/tools/session.js +16 -10
- package/package.json +1 -1
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/tool-registry.d.ts
CHANGED
|
@@ -7,8 +7,16 @@ export declare class ToolRegistry {
|
|
|
7
7
|
private tools;
|
|
8
8
|
private handlers;
|
|
9
9
|
private enricher?;
|
|
10
|
+
private autoSessionPromise;
|
|
10
11
|
/** Set the context enricher */
|
|
11
12
|
setEnricher(enricher: ContextEnricher): void;
|
|
13
|
+
/**
|
|
14
|
+
* Ensure an active session exists before handling a tool call.
|
|
15
|
+
* Skips for session management tools. Handles post-end_session restart.
|
|
16
|
+
*/
|
|
17
|
+
private ensureSession;
|
|
18
|
+
/** Start an auto-session via the RAG API */
|
|
19
|
+
private doAutoStartSession;
|
|
12
20
|
/** Register a tool module */
|
|
13
21
|
register(module: ToolModule): void;
|
|
14
22
|
/** Get all registered tool definitions */
|
package/dist/tool-registry.js
CHANGED
|
@@ -12,6 +12,12 @@ const TRACKING_EXCLUDE = new Set([
|
|
|
12
12
|
"get_prediction_stats",
|
|
13
13
|
"get_rag_guidelines",
|
|
14
14
|
]);
|
|
15
|
+
/** Session management tools — skip auto-session to avoid recursion */
|
|
16
|
+
const SESSION_TOOLS = new Set([
|
|
17
|
+
"start_session",
|
|
18
|
+
"end_session",
|
|
19
|
+
"get_session_context",
|
|
20
|
+
]);
|
|
15
21
|
/** Summarize tool args into a short string for analytics */
|
|
16
22
|
function summarizeInput(name, args) {
|
|
17
23
|
// Common patterns: query, question, content, feature, code, file
|
|
@@ -48,10 +54,54 @@ export class ToolRegistry {
|
|
|
48
54
|
tools = [];
|
|
49
55
|
handlers = new Map();
|
|
50
56
|
enricher;
|
|
57
|
+
autoSessionPromise = null;
|
|
51
58
|
/** Set the context enricher */
|
|
52
59
|
setEnricher(enricher) {
|
|
53
60
|
this.enricher = enricher;
|
|
54
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Ensure an active session exists before handling a tool call.
|
|
64
|
+
* Skips for session management tools. Handles post-end_session restart.
|
|
65
|
+
*/
|
|
66
|
+
async ensureSession(ctx) {
|
|
67
|
+
if (ctx.activeSessionId)
|
|
68
|
+
return;
|
|
69
|
+
// If already starting, wait for it
|
|
70
|
+
if (this.autoSessionPromise) {
|
|
71
|
+
await this.autoSessionPromise;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.autoSessionPromise = this.doAutoStartSession(ctx);
|
|
75
|
+
try {
|
|
76
|
+
await this.autoSessionPromise;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
this.autoSessionPromise = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Start an auto-session via the RAG API */
|
|
83
|
+
async doAutoStartSession(ctx) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await ctx.api.post("/api/session/start", {
|
|
86
|
+
projectName: ctx.projectName,
|
|
87
|
+
initialContext: "auto-started by MCP tool call",
|
|
88
|
+
});
|
|
89
|
+
const session = response.data?.session;
|
|
90
|
+
const sid = session?.sessionId || response.data?.sessionId;
|
|
91
|
+
if (sid) {
|
|
92
|
+
ctx.activeSessionId = sid;
|
|
93
|
+
}
|
|
94
|
+
// Fire-and-forget: ensure critical collections exist
|
|
95
|
+
ctx.api
|
|
96
|
+
.post("/api/ensure-collections", { projectName: ctx.projectName })
|
|
97
|
+
.catch(() => {
|
|
98
|
+
// Silent — must never break tool execution
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Silent — auto-session must never block tool execution
|
|
103
|
+
}
|
|
104
|
+
}
|
|
55
105
|
/** Register a tool module */
|
|
56
106
|
register(module) {
|
|
57
107
|
this.tools.push(...module.tools);
|
|
@@ -88,6 +138,10 @@ export class ToolRegistry {
|
|
|
88
138
|
if (!handler) {
|
|
89
139
|
return `Unknown tool: ${name}`;
|
|
90
140
|
}
|
|
141
|
+
// Auto-start session on first tool call (skip for session management tools)
|
|
142
|
+
if (!SESSION_TOOLS.has(name)) {
|
|
143
|
+
await this.ensureSession(ctx);
|
|
144
|
+
}
|
|
91
145
|
const startTime = Date.now();
|
|
92
146
|
try {
|
|
93
147
|
// Before: auto-enrich context
|
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/dist/tools/session.js
CHANGED
|
@@ -270,26 +270,32 @@ export function createSessionTools(projectName, sharedCtx) {
|
|
|
270
270
|
resumeFrom,
|
|
271
271
|
});
|
|
272
272
|
const data = response.data;
|
|
273
|
+
const session = data.session;
|
|
274
|
+
// Extract fields — API returns { session: { sessionId, startedAt, ... } }
|
|
275
|
+
const sid = session?.sessionId || data.sessionId;
|
|
276
|
+
const started = session?.startedAt || data.started;
|
|
277
|
+
const resumedFrom = session?.metadata?.resumedFrom || data.resumedFrom;
|
|
278
|
+
const initialFiles = session?.currentFiles || data.initialFiles;
|
|
273
279
|
// Update shared context with active session ID
|
|
274
|
-
if (sharedCtx &&
|
|
275
|
-
sharedCtx.activeSessionId =
|
|
280
|
+
if (sharedCtx && sid) {
|
|
281
|
+
sharedCtx.activeSessionId = sid;
|
|
276
282
|
}
|
|
277
283
|
let result = `**Session Started**\n\n`;
|
|
278
|
-
result += `- **Session ID:** ${
|
|
279
|
-
result += `- **Started:** ${
|
|
280
|
-
if (
|
|
281
|
-
result += `- **Resumed From:** ${
|
|
284
|
+
result += `- **Session ID:** ${sid}\n`;
|
|
285
|
+
result += `- **Started:** ${started}\n`;
|
|
286
|
+
if (resumedFrom) {
|
|
287
|
+
result += `- **Resumed From:** ${resumedFrom}\n`;
|
|
282
288
|
}
|
|
283
|
-
if (
|
|
289
|
+
if (initialFiles && initialFiles.length > 0) {
|
|
284
290
|
result += `\n**Initial Files:**\n`;
|
|
285
|
-
result +=
|
|
291
|
+
result += initialFiles
|
|
286
292
|
.map((f) => `- ${f}`)
|
|
287
293
|
.join("\n");
|
|
288
294
|
result += "\n";
|
|
289
295
|
}
|
|
290
296
|
// Include prefetch stats if available
|
|
291
|
-
if (
|
|
292
|
-
const pf =
|
|
297
|
+
if (session?.metadata?.prefetchStats) {
|
|
298
|
+
const pf = session.metadata.prefetchStats;
|
|
293
299
|
result += `\n**Predictive Prefetch:** ${pf.prefetchedCount ?? 0} resources prefetched\n`;
|
|
294
300
|
}
|
|
295
301
|
// Include briefing if available (Sprint E)
|