smart-context-mcp 1.13.0 → 1.14.0

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/README.md CHANGED
@@ -56,7 +56,7 @@ Restart your AI client. Done.
56
56
  # Check installed version
57
57
  npm list -g smart-context-mcp
58
58
 
59
- # Should show: smart-context-mcp@1.13.0 (or later)
59
+ # Should show: smart-context-mcp@1.14.0 (or later)
60
60
 
61
61
  # Update to latest version
62
62
  npm update -g smart-context-mcp
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
3
  "mcpName": "io.github.Arrayo/smart-context-mcp",
4
- "version": "1.13.0",
4
+ "version": "1.14.0",
5
5
  "description": "MCP server that reduces agent token usage by 90% with intelligent context compression, task checkpoint persistence, and workflow-aware agent guidance.",
6
6
  "author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
7
7
  "type": "module",
@@ -60,6 +60,7 @@
60
60
  "init:clients": "node ./scripts/init-clients.js",
61
61
  "smoke:formats": "node ./scripts/format-smoke.js",
62
62
  "test": "node --test --test-concurrency=1 ./tests/*.test.js",
63
+ "test:fast": "node --test --test-concurrency=4 ./tests/*.test.js",
63
64
  "verify": "node ./scripts/verify-features-direct.js",
64
65
  "benchmark": "node ./scripts/run-benchmark.js",
65
66
  "benchmark:orchestration": "node ./evals/orchestration-benchmark.js",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/Arrayo/smart-context-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.13.0",
9
+ "version": "1.14.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "smart-context-mcp",
14
- "version": "1.13.0",
14
+ "version": "1.14.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
package/src/server.js CHANGED
@@ -146,13 +146,14 @@ export const createDevctxServer = () => {
146
146
 
147
147
  server.tool(
148
148
  'smart_search',
149
- 'Search code across the project using ripgrep (with filesystem fallback). Returns grouped, ranked results. Optional intent (implementation/debug/tests/config/docs/explore) adjusts ranking. Use instead of native Grep for ranked, deduplicated results with index boosting.',
149
+ 'Search code with ranked, deduplicated results and index boosting. Best for: finding where a symbol is defined/used, understanding call chains, locating implementations. NOT ideal for: exact string matching (use Grep), finding files by name (use Glob), broad multi-word queries (generates noise). Optional intent adjusts ranking. maxFiles caps the number of files returned (default 15). When >30 files match, results include a hint suggesting Grep instead.',
150
150
  {
151
151
  query: z.string(),
152
152
  cwd: z.string().optional(),
153
153
  intent: z.enum(['implementation', 'debug', 'tests', 'config', 'docs', 'explore']).optional(),
154
+ maxFiles: z.number().int().min(1).max(50).optional(),
154
155
  },
155
- async ({ query, cwd = '.', intent }) => asTextResult(await smartSearch({ query, cwd, intent })),
156
+ async ({ query, cwd = '.', intent, maxFiles }) => asTextResult(await smartSearch({ query, cwd, intent, maxFiles })),
156
157
  );
157
158
 
158
159
  server.tool(
@@ -324,16 +324,20 @@ const buildZeroResultsMessage = (query, searchMode, provenance) => {
324
324
  return lines.join('\n');
325
325
  };
326
326
 
327
- const buildCompactResult = (groups, totalMatches, query, root, searchMode, provenance) => {
327
+ const MAX_RESULT_FILES = 15;
328
+
329
+ const buildCompactResult = (groups, totalMatches, query, root, searchMode, provenance, totalFiles) => {
328
330
  if (totalMatches === 0) {
329
331
  return buildZeroResultsMessage(query, searchMode, provenance);
330
332
  }
331
333
 
332
334
  const modeLabel = searchMode === 'exact' ? '' : searchMode === 'regex' ? ' [regex fallback]' : ` [term expansion: ${(provenance?.expandedTerms ?? []).join(', ')}]`;
333
335
 
336
+ const topGroups = groups.slice(0, MAX_RESULT_FILES);
337
+
334
338
  if (totalMatches <= 20) {
335
339
  const header = modeLabel ? `# Search mode:${modeLabel}\n\n` : '';
336
- return header + groups
340
+ return header + topGroups
337
341
  .flatMap((group) => group.matches)
338
342
  .map(formatMatch)
339
343
  .join('\n');
@@ -341,29 +345,34 @@ const buildCompactResult = (groups, totalMatches, query, root, searchMode, prove
341
345
 
342
346
  const lines = [
343
347
  `query: ${query}${modeLabel}`,
344
- `root: ${root}`,
345
- `total matches: ${totalMatches}`,
346
- `matched files: ${groups.length}`,
348
+ `total: ${totalMatches} matches in ${totalFiles ?? groups.length} files${totalFiles && totalFiles > groups.length ? ` (showing top ${groups.length})` : ''}`,
347
349
  '',
348
350
  '# Top files',
349
351
  ];
350
352
 
351
- for (const group of groups.slice(0, 10)) {
353
+ for (const group of topGroups.slice(0, 10)) {
352
354
  lines.push(`${group.count} match(es), score ${group.score} :: ${group.file}`);
353
355
  }
354
356
 
355
357
  lines.push('', '# Sample matches');
356
358
 
357
- for (const group of groups.slice(0, 5)) {
358
- for (const match of group.matches.slice(0, 3)) {
359
+ const topScore = topGroups[0]?.score ?? 0;
360
+ for (const group of topGroups.slice(0, 5)) {
361
+ const linesPerFile = group.score >= topScore * 0.7 ? 5 : 2;
362
+ for (const match of group.matches.slice(0, linesPerFile)) {
359
363
  lines.push(formatMatch(match));
360
364
  }
361
365
  }
362
366
 
367
+ const fileCount = totalFiles ?? groups.length;
368
+ if (fileCount > 30) {
369
+ lines.push('', `# Note: ${fileCount} files matched — query may be too broad. Use Grep for exact pattern matching.`);
370
+ }
371
+
363
372
  return lines.join('\n');
364
373
  };
365
374
 
366
- export const smartSearch = async ({ query, cwd = '.', intent, _testForceWalk = false, progress: enableProgress = false }) => {
375
+ export const smartSearch = async ({ query, cwd = '.', intent, maxFiles, _testForceWalk = false, progress: enableProgress = false }) => {
367
376
  const progress = enableProgress ? createProgressReporter('smart_search') : null;
368
377
  const startTime = Date.now();
369
378
 
@@ -463,8 +472,11 @@ export const smartSearch = async ({ query, cwd = '.', intent, _testForceWalk = f
463
472
  }
464
473
  }
465
474
 
475
+ const effectiveMaxFiles = maxFiles ?? MAX_RESULT_FILES;
476
+ const cappedGroups = groups.slice(0, effectiveMaxFiles);
477
+
466
478
  const rawText = dedupedMatches.map(formatMatch).join('\n');
467
- const compressedText = truncate(buildCompactResult(groups, dedupedMatches.length, query, root, searchMode, provenance), 5000);
479
+ const compressedText = truncate(buildCompactResult(cappedGroups, dedupedMatches.length, query, root, searchMode, provenance, groups.length), 5000);
468
480
  const metrics = buildMetrics({
469
481
  tool: 'smart_search',
470
482
  target: `${root} :: ${query}`,
@@ -522,7 +534,7 @@ export const smartSearch = async ({ query, cwd = '.', intent, _testForceWalk = f
522
534
  ...(indexHits ? { indexBoosted: indexHits.size } : {}),
523
535
  totalMatches: dedupedMatches.length,
524
536
  matchedFiles: groups.length,
525
- topFiles: groups.slice(0, 10).map((group) => ({ file: group.file, count: group.count, score: group.score })),
537
+ topFiles: cappedGroups.slice(0, 10).map((group) => ({ file: group.file, count: group.count, score: group.score })),
526
538
  matches: compressedText,
527
539
  };
528
540