smart-context-mcp 1.14.0 → 1.16.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 +1 -1
- package/package.json +3 -1
- package/server.json +2 -2
- package/src/server.js +1 -1
- package/src/tools/smart-context.js +7 -9
- package/src/tools/smart-read/code.js +18 -0
- package/src/tools/smart-search.js +2 -1
- package/src/utils/context-scoring.js +1 -0
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.
|
|
59
|
+
# Should show: smart-context-mcp@1.16.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.
|
|
4
|
+
"version": "1.16.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",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"eval:context": "node ./evals/harness.js --tool=context",
|
|
70
70
|
"eval:both": "node ./evals/harness.js --tool=both",
|
|
71
71
|
"eval:self": "node ./evals/harness.js --root=../.. --corpus=./evals/corpus/self-tasks.json",
|
|
72
|
+
"eval:realworld": "node ./evals/realworld-eval.js",
|
|
73
|
+
"eval:realworld:json": "node ./evals/realworld-eval.js --json",
|
|
72
74
|
"eval:report": "node ./evals/report.js",
|
|
73
75
|
"report:metrics": "node ./scripts/report-metrics.js",
|
|
74
76
|
"report:workflows": "node ./scripts/report-workflow-metrics.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.
|
|
9
|
+
"version": "1.16.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "smart-context-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.16.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/src/server.js
CHANGED
|
@@ -158,7 +158,7 @@ export const createDevctxServer = () => {
|
|
|
158
158
|
|
|
159
159
|
server.tool(
|
|
160
160
|
'smart_context',
|
|
161
|
-
'PREFERRED for multi-file tasks. Gets curated context in one call — replaces the manual search → read → read cycle. Combines search + graph expansion + selective reading.
|
|
161
|
+
'PREFERRED for multi-file tasks. Gets curated context in one call — replaces the manual search → read → read cycle. Combines search + graph expansion + selective reading. Primary files always include content (signatures) in balanced mode — reduces follow-up smart_read calls. Options: intent, maxTokens (budget, default 12000), diff (true for HEAD or branch name), detail (minimal/balanced/deep), include (content/graph/hints/symbolDetail), prefetch (true for predictive loading). Call this FIRST before individual smart_read/smart_search calls.',
|
|
162
162
|
{
|
|
163
163
|
task: z.string(),
|
|
164
164
|
intent: z.enum(['implementation', 'debug', 'tests', 'config', 'docs', 'explore']).optional(),
|
|
@@ -196,9 +196,7 @@ export const allocateReads = (files, maxTokens, intent, detailMode = 'balanced')
|
|
|
196
196
|
|
|
197
197
|
const mode = detailMode === 'deep'
|
|
198
198
|
? 'full'
|
|
199
|
-
:
|
|
200
|
-
? 'outline'
|
|
201
|
-
: 'signatures';
|
|
199
|
+
: 'signatures';
|
|
202
200
|
|
|
203
201
|
roleLimits[best.role]--;
|
|
204
202
|
selected.push(best);
|
|
@@ -269,8 +267,7 @@ const shouldReadContentForItem = (item, payload, detailMode, includeSet, intent)
|
|
|
269
267
|
const strongIndexSignal = hasStrongIndexSignal(payload);
|
|
270
268
|
|
|
271
269
|
if (item.role === 'primary') {
|
|
272
|
-
|
|
273
|
-
return !strongIndexSignal;
|
|
270
|
+
return true;
|
|
274
271
|
}
|
|
275
272
|
|
|
276
273
|
if (item.role === 'test' && intent === 'tests') {
|
|
@@ -278,6 +275,7 @@ const shouldReadContentForItem = (item, payload, detailMode, includeSet, intent)
|
|
|
278
275
|
}
|
|
279
276
|
|
|
280
277
|
if (item.role === 'dependency') {
|
|
278
|
+
if ((item.matchedSymbols?.length ?? 0) > 0) return true;
|
|
281
279
|
return !strongIndexSignal && (payload.symbols?.length ?? 0) === 0;
|
|
282
280
|
}
|
|
283
281
|
|
|
@@ -353,7 +351,7 @@ const DEFAULT_INCLUDE = ['content', 'graph', 'hints', 'symbolDetail'];
|
|
|
353
351
|
export const smartContext = async ({
|
|
354
352
|
task,
|
|
355
353
|
intent,
|
|
356
|
-
maxTokens =
|
|
354
|
+
maxTokens = 12000,
|
|
357
355
|
entryFile,
|
|
358
356
|
diff,
|
|
359
357
|
detail = 'balanced',
|
|
@@ -518,7 +516,7 @@ export const smartContext = async ({
|
|
|
518
516
|
primarySeeds.unshift({ rel, absPath: abs, evidence: [{ type: 'entryFile' }] });
|
|
519
517
|
}
|
|
520
518
|
}
|
|
521
|
-
} catch {
|
|
519
|
+
} catch (err) { process.stderr.write(`[devctx] smart_context: entryFile "${entryFile}" skipped: ${err.message}\n`); }
|
|
522
520
|
}
|
|
523
521
|
|
|
524
522
|
await ensureIndexReady({ root });
|
|
@@ -549,7 +547,7 @@ export const smartContext = async ({
|
|
|
549
547
|
});
|
|
550
548
|
}
|
|
551
549
|
}
|
|
552
|
-
} catch {}
|
|
550
|
+
} catch (err) { process.stderr.write(`[devctx] smart_context: prefetch path "${predicted.path}" skipped: ${err.message}\n`); }
|
|
553
551
|
}
|
|
554
552
|
}
|
|
555
553
|
} catch (error) {
|
|
@@ -759,7 +757,7 @@ export const smartContext = async ({
|
|
|
759
757
|
order: idx
|
|
760
758
|
}))
|
|
761
759
|
});
|
|
762
|
-
} catch {}
|
|
760
|
+
} catch (err) { process.stderr.write(`[devctx] smart_context: recordContextAccess failed: ${err.message}\n`); }
|
|
763
761
|
}
|
|
764
762
|
|
|
765
763
|
const COVERAGE_RANK = { full: 2, partial: 1, none: 0 };
|
|
@@ -114,6 +114,18 @@ const getFunctionSignature = (statement, sourceFile) => {
|
|
|
114
114
|
return sig.length > 120 ? `${sig.slice(0, 120)}...` : sig;
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
const getVariableFunctionSignature = (declaration, sourceFile) => {
|
|
118
|
+
const init = declaration.initializer;
|
|
119
|
+
if (!init) return null;
|
|
120
|
+
if (!ts.isArrowFunction(init) && !ts.isFunctionExpression(init)) return null;
|
|
121
|
+
const body = init.body;
|
|
122
|
+
if (!body) return null;
|
|
123
|
+
const fullText = declaration.getText(sourceFile);
|
|
124
|
+
const bodyOffset = body.getStart(sourceFile) - declaration.getStart(sourceFile);
|
|
125
|
+
const sig = fullText.slice(0, bodyOffset).replace(/\s+$/, '');
|
|
126
|
+
return sig.length > 120 ? `${sig.slice(0, 120)}...` : sig;
|
|
127
|
+
};
|
|
128
|
+
|
|
117
129
|
const formatTopLevelStatement = (statement, sourceFile, mode = 'outline') => {
|
|
118
130
|
const exported = statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
119
131
|
const prefix = exported ? 'export ' : '';
|
|
@@ -149,6 +161,12 @@ const formatTopLevelStatement = (statement, sourceFile, mode = 'outline') => {
|
|
|
149
161
|
: statement.declarationList.flags & ts.NodeFlags.Let
|
|
150
162
|
? 'let'
|
|
151
163
|
: 'var';
|
|
164
|
+
|
|
165
|
+
if (mode === 'signatures' && statement.declarationList.declarations.length === 1) {
|
|
166
|
+
const sig = getVariableFunctionSignature(statement.declarationList.declarations[0], sourceFile);
|
|
167
|
+
if (sig) return `${prefix}${declarationKind} ${sig}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
152
170
|
return `${prefix}${declarationKind} ${collectVariableNames(statement.declarationList).join(', ')}`;
|
|
153
171
|
}
|
|
154
172
|
|
|
@@ -533,7 +533,8 @@ export const smartSearch = async ({ query, cwd = '.', intent, maxFiles, _testFor
|
|
|
533
533
|
...(validIntent ? { intent: validIntent } : {}),
|
|
534
534
|
...(indexHits ? { indexBoosted: indexHits.size } : {}),
|
|
535
535
|
totalMatches: dedupedMatches.length,
|
|
536
|
-
matchedFiles:
|
|
536
|
+
matchedFiles: cappedGroups.length,
|
|
537
|
+
...(groups.length > cappedGroups.length ? { totalFiles: groups.length } : {}),
|
|
537
538
|
topFiles: cappedGroups.slice(0, 10).map((group) => ({ file: group.file, count: group.count, score: group.score })),
|
|
538
539
|
matches: compressedText,
|
|
539
540
|
};
|
|
@@ -277,6 +277,7 @@ export const scorePrimarySeed = (seed, task, intent) => {
|
|
|
277
277
|
let score = 0;
|
|
278
278
|
|
|
279
279
|
for (const evidence of seed.evidence ?? []) {
|
|
280
|
+
if (evidence.type === 'entryFile') { score += 100; continue; }
|
|
280
281
|
if (evidence.type !== 'searchHit') continue;
|
|
281
282
|
score += Math.max(0, 40 - ((evidence.rank ?? 1) - 1) * 8);
|
|
282
283
|
if (!evidence.query) continue;
|