smart-context-mcp 1.3.1 → 1.5.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
@@ -1,6 +1,6 @@
1
1
  # smart-context-mcp
2
2
 
3
- MCP server that reduces AI agent token usage by 90% with intelligent context compression.
3
+ MCP server that reduces AI agent token usage by up to 90% with intelligent context compression (measured on this project).
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/smart-context-mcp.svg)](https://www.npmjs.com/package/smart-context-mcp)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -42,6 +42,113 @@ npx smart-context-init --target .
42
42
  ```
43
43
  Restart your AI client. Done.
44
44
 
45
+ ---
46
+
47
+ ## 📊 Real Metrics
48
+
49
+ **Production use on this project:**
50
+ - ~7M tokens → ~800K tokens (approximately 89% reduction)
51
+ - 1,500+ operations tracked
52
+ - Compression ratios: 3x to 46x
53
+
54
+ **Workflow savings:**
55
+ - Debugging: ~85-90% reduction
56
+ - Code Review: ~85-90% reduction
57
+ - Refactoring: ~85-90% reduction
58
+ - Testing: ~85-90% reduction
59
+ - Architecture: ~85-90% reduction
60
+
61
+ **Real adoption:**
62
+ - Approximately 70-75% of complex tasks use devctx
63
+ - Top tools: `smart_read` (850+), `smart_search` (280+), `smart_shell` (220+)
64
+ - Non-usage: task too simple, no index built, native tools preferred
65
+
66
+ ---
67
+
68
+ ## 🚀 How to Invoke the MCP
69
+
70
+ The MCP doesn't intercept prompts automatically. **You need to tell the agent to use it.**
71
+
72
+ ### Option 1: Use MCP Prompts (Easiest)
73
+
74
+ In Cursor, type in the chat:
75
+
76
+ ```
77
+ /prompt use-devctx
78
+
79
+ [Your task here]
80
+ ```
81
+
82
+ **Available prompts:**
83
+ - `/prompt use-devctx` - Force devctx tools for current task
84
+ - `/prompt devctx-workflow` - Full workflow (start → context → work → end)
85
+ - `/prompt devctx-preflight` - Preflight only (build_index + smart_turn start)
86
+
87
+ ### Option 2: Explicit Instruction
88
+
89
+ Just tell the agent directly:
90
+
91
+ ```
92
+ Use smart_turn(start) to recover context, then [your task]
93
+ ```
94
+
95
+ Or:
96
+
97
+ ```
98
+ Use the MCP to review this code
99
+ ```
100
+
101
+ ### Option 3: Automatic (via Rules)
102
+
103
+ The agent *should* use devctx automatically for complex tasks because:
104
+ - ✅ `.cursorrules` is active in Cursor
105
+ - ✅ `CLAUDE.md` is active in Claude Desktop (if you created it)
106
+ - ✅ `AGENTS.md` is active in other clients (if you created it)
107
+
108
+ **But it's not guaranteed** - the agent decides based on task complexity.
109
+
110
+ ### ⚡ Quick Reference
111
+
112
+ | Scenario | Command |
113
+ |----------|---------|
114
+ | Start new task | `/prompt devctx-workflow` |
115
+ | Continue previous task | `smart_turn(start) and continue` |
116
+ | Force MCP usage | `/prompt use-devctx` |
117
+ | First time in project | `/prompt devctx-preflight` |
118
+ | Trust automatic rules | Just describe your task normally |
119
+
120
+ ---
121
+
122
+ ## 🚨 Agent Ignored devctx? → Paste This Next
123
+
124
+ <table>
125
+ <tr>
126
+ <td width="100%" bgcolor="#FFF3CD">
127
+
128
+ ### 📋 Official Prompt (Copy & Paste)
129
+
130
+ ```
131
+ Use smart-context-mcp for this task.
132
+ Start with smart_turn(start), then use smart_context or smart_search before reading full files.
133
+ End with smart_turn(end) if you make progress.
134
+ ```
135
+
136
+ ### ⚡ Ultra-Short
137
+
138
+ ```
139
+ Use devctx: smart_turn(start) → smart_context → smart_turn(end)
140
+ ```
141
+
142
+ </td>
143
+ </tr>
144
+ </table>
145
+
146
+ **When:** Agent read large files with `Read`, used `Grep` repeatedly, or no devctx tools in complex task.
147
+
148
+ **Why:** Task seemed simple, no index built, native tools appeared more direct, or rules weren't strong enough.
149
+
150
+ ---
151
+
45
152
  ## How it Works in Practice
46
153
 
47
154
  **The reality:** This MCP does not intercept prompts automatically. Here's the actual flow:
@@ -267,19 +374,50 @@ Get everything for a task in one call:
267
374
 
268
375
  Returns: relevant files + compressed content + symbol details + graph relationships
269
376
 
377
+ **Smart pattern detection:** Automatically detects literal patterns (TODO, FIXME, /**, console.log, debugger) and prioritizes them in search.
378
+
270
379
  ### smart_summary
271
380
 
272
381
  Maintain task checkpoint:
273
382
 
274
383
  ```javascript
275
- // Save checkpoint
384
+ // Save checkpoint (flat API - recommended)
385
+ { action: 'update', goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }
386
+
387
+ // Or nested format (backward compatible)
276
388
  { action: 'update', update: { goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }}
277
389
 
278
390
  // Resume task
279
391
  { action: 'get' }
280
392
  ```
281
393
 
282
- Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation.
394
+ Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation. Supports both flat and nested parameter formats.
395
+
396
+ ### smart_status
397
+
398
+ Display current session context:
399
+
400
+ ```javascript
401
+ { format: 'detailed' } // Full output with progress stats
402
+ { format: 'compact' } // Minimal JSON
403
+ ```
404
+
405
+ Shows goal, status, recent decisions, touched files, and progress. Updates automatically with each MCP operation.
406
+
407
+ ### smart_edit
408
+
409
+ Batch edit multiple files:
410
+
411
+ ```javascript
412
+ {
413
+ pattern: 'console.log',
414
+ replacement: 'logger.info',
415
+ files: ['src/a.js', 'src/b.js'],
416
+ mode: 'literal' // or 'regex'
417
+ }
418
+ ```
419
+
420
+ Use `dryRun: true` for preview. Max 50 files per call.
283
421
 
284
422
  ## New Features
285
423
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "MCP server that reduces agent token usage by 90% with intelligent context compression, task checkpoint persistence, and workflow-aware agent guidance.",
5
5
  "author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
6
6
  "type": "module",
@@ -31,7 +31,8 @@
31
31
  "scripts/headless-wrapper.js",
32
32
  "scripts/init-clients.js",
33
33
  "scripts/report-metrics.js",
34
- "scripts/report-workflow-metrics.js"
34
+ "scripts/report-workflow-metrics.js",
35
+ "scripts/report-adoption-metrics.js"
35
36
  ],
36
37
  "engines": {
37
38
  "node": ">=18"
@@ -60,7 +61,8 @@
60
61
  "eval:self": "node ./evals/harness.js --root=../.. --corpus=./evals/corpus/self-tasks.json",
61
62
  "eval:report": "node ./evals/report.js",
62
63
  "report:metrics": "node ./scripts/report-metrics.js",
63
- "report:workflows": "node ./scripts/report-workflow-metrics.js"
64
+ "report:workflows": "node ./scripts/report-workflow-metrics.js",
65
+ "report:adoption": "node ./scripts/report-adoption-metrics.js"
64
66
  },
65
67
  "dependencies": {
66
68
  "@modelcontextprotocol/sdk": "^1.13.0",
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Report adoption metrics - measures real MCP usage in non-trivial tasks
5
+ *
6
+ * Usage:
7
+ * npm run report:adoption
8
+ * npm run report:adoption -- --days 7
9
+ * npm run report:adoption -- --json
10
+ */
11
+
12
+ import { withStateDb } from '../src/storage/sqlite.js';
13
+ import { WORKFLOW_DEFINITIONS } from '../src/workflow-tracker.js';
14
+
15
+ const parseArgs = (argv) => {
16
+ const args = {};
17
+ for (let i = 2; i < argv.length; i++) {
18
+ if (argv[i] === '--days' && argv[i + 1]) {
19
+ args.days = parseInt(argv[i + 1], 10);
20
+ i++;
21
+ } else if (argv[i] === '--json') {
22
+ args.json = true;
23
+ }
24
+ }
25
+ return args;
26
+ };
27
+
28
+ const formatPct = (value) => `${value.toFixed(1)}%`;
29
+ const formatNumber = (value) => new Intl.NumberFormat('en-US').format(value);
30
+
31
+ /**
32
+ * Classify if a session represents a non-trivial task
33
+ */
34
+ const isNonTrivialTask = (sessionEvents, metricsEvents) => {
35
+ // Criteria 1: Multiple operations (≥5)
36
+ if (sessionEvents.length + metricsEvents.length >= 5) return true;
37
+
38
+ // Criteria 2: Large file reads (any file >500 lines)
39
+ const hasLargeFileRead = metricsEvents.some(
40
+ (m) => m.tool === 'Read' && m.raw_tokens > 1500 // ~500 lines
41
+ );
42
+ if (hasLargeFileRead) return true;
43
+
44
+ // Criteria 3: Multiple file reads (≥3)
45
+ const fileReads = metricsEvents.filter((m) => m.tool === 'Read' || m.tool === 'smart_read');
46
+ if (fileReads.length >= 3) return true;
47
+
48
+ // Criteria 4: Repeated searches (≥2)
49
+ const searches = metricsEvents.filter((m) => m.tool === 'Grep' || m.tool === 'smart_search');
50
+ if (searches.length >= 2) return true;
51
+
52
+ // Criteria 5: Workflow classification
53
+ const devctxTools = metricsEvents.filter((m) =>
54
+ ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'].includes(m.tool)
55
+ );
56
+ if (devctxTools.length > 0) return true;
57
+
58
+ return false;
59
+ };
60
+
61
+ /**
62
+ * Check if session used devctx tools
63
+ */
64
+ const usedDevctx = (metricsEvents) => {
65
+ const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell', 'smart_read_batch'];
66
+ return metricsEvents.some((m) => devctxTools.includes(m.tool));
67
+ };
68
+
69
+ /**
70
+ * Calculate adoption metrics
71
+ */
72
+ const calculateAdoptionMetrics = (days = 30) => {
73
+ return withStateDb((db) => {
74
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
75
+
76
+ // Get all sessions since cutoff
77
+ const sessions = db
78
+ .prepare(
79
+ `
80
+ SELECT session_id, snapshot_json, created_at
81
+ FROM sessions
82
+ WHERE created_at >= ?
83
+ ORDER BY created_at DESC
84
+ `
85
+ )
86
+ .all(cutoff);
87
+
88
+ const results = {
89
+ totalSessions: sessions.length,
90
+ nonTrivialTasks: 0,
91
+ tasksWithDevctx: 0,
92
+ adoptionRate: 0,
93
+ byWorkflow: {},
94
+ toolUsage: {},
95
+ };
96
+
97
+ // Initialize workflow stats
98
+ Object.keys(WORKFLOW_DEFINITIONS).forEach((type) => {
99
+ results.byWorkflow[type] = {
100
+ total: 0,
101
+ withDevctx: 0,
102
+ adoptionRate: 0,
103
+ };
104
+ });
105
+
106
+ // Analyze each session
107
+ sessions.forEach((session) => {
108
+ const snapshot = JSON.parse(session.snapshot_json || '{}');
109
+ const sessionId = session.session_id;
110
+
111
+ // Get events for this session
112
+ const sessionEvents = db
113
+ .prepare('SELECT * FROM session_events WHERE session_id = ?')
114
+ .all(sessionId);
115
+
116
+ const metricsEvents = db
117
+ .prepare('SELECT * FROM metrics_events WHERE session_id = ?')
118
+ .all(sessionId);
119
+
120
+ // Check if non-trivial
121
+ if (!isNonTrivialTask(sessionEvents, metricsEvents)) {
122
+ return;
123
+ }
124
+
125
+ results.nonTrivialTasks++;
126
+
127
+ // Check if used devctx
128
+ const hasDevctx = usedDevctx(metricsEvents);
129
+ if (hasDevctx) {
130
+ results.tasksWithDevctx++;
131
+ }
132
+
133
+ // Track tool usage
134
+ metricsEvents.forEach((m) => {
135
+ results.toolUsage[m.tool] = (results.toolUsage[m.tool] || 0) + 1;
136
+ });
137
+
138
+ // Classify by workflow if possible
139
+ const goal = snapshot.goal || '';
140
+ let workflowType = null;
141
+
142
+ for (const [type, def] of Object.entries(WORKFLOW_DEFINITIONS)) {
143
+ if (def.pattern.test(goal)) {
144
+ workflowType = type;
145
+ break;
146
+ }
147
+ }
148
+
149
+ if (workflowType) {
150
+ results.byWorkflow[workflowType].total++;
151
+ if (hasDevctx) {
152
+ results.byWorkflow[workflowType].withDevctx++;
153
+ }
154
+ }
155
+ });
156
+
157
+ // Calculate rates
158
+ if (results.nonTrivialTasks > 0) {
159
+ results.adoptionRate = (results.tasksWithDevctx / results.nonTrivialTasks) * 100;
160
+ }
161
+
162
+ Object.keys(results.byWorkflow).forEach((type) => {
163
+ const stats = results.byWorkflow[type];
164
+ if (stats.total > 0) {
165
+ stats.adoptionRate = (stats.withDevctx / stats.total) * 100;
166
+ }
167
+ });
168
+
169
+ return results;
170
+ });
171
+ };
172
+
173
+ /**
174
+ * Format and print report
175
+ */
176
+ const printReport = (metrics, days) => {
177
+ console.log(`\nAdoption Metrics (Last ${days} Days)`);
178
+ console.log('='.repeat(50));
179
+ console.log();
180
+
181
+ console.log(`Total Sessions: ${formatNumber(metrics.totalSessions)}`);
182
+ console.log(`Non-Trivial Tasks: ${formatNumber(metrics.nonTrivialTasks)}`);
183
+ console.log(`Tasks with devctx: ${formatNumber(metrics.tasksWithDevctx)}`);
184
+ console.log();
185
+
186
+ console.log(`Overall Adoption: ${formatPct(metrics.adoptionRate)}`);
187
+ console.log();
188
+
189
+ console.log('By Workflow:');
190
+ Object.entries(metrics.byWorkflow)
191
+ .filter(([, stats]) => stats.total > 0)
192
+ .sort((a, b) => b[1].adoptionRate - a[1].adoptionRate)
193
+ .forEach(([type, stats]) => {
194
+ const def = WORKFLOW_DEFINITIONS[type];
195
+ console.log(
196
+ ` ${def.name.padEnd(25)} ${formatPct(stats.adoptionRate).padStart(7)} (${stats.withDevctx}/${stats.total})`
197
+ );
198
+ });
199
+ console.log();
200
+
201
+ console.log('Top devctx Tools:');
202
+ const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'];
203
+ Object.entries(metrics.toolUsage)
204
+ .filter(([tool]) => devctxTools.includes(tool))
205
+ .sort((a, b) => b[1] - a[1])
206
+ .slice(0, 5)
207
+ .forEach(([tool, count]) => {
208
+ console.log(` ${tool.padEnd(20)} ${formatNumber(count)} uses`);
209
+ });
210
+ console.log();
211
+ };
212
+
213
+ // Main
214
+ const args = parseArgs(process.argv);
215
+ const days = args.days || 30;
216
+
217
+ try {
218
+ const metrics = calculateAdoptionMetrics(days);
219
+
220
+ if (args.json) {
221
+ console.log(JSON.stringify(metrics, null, 2));
222
+ } else {
223
+ printReport(metrics, days);
224
+ }
225
+ } catch (error) {
226
+ console.error('Error calculating adoption metrics:', error.message);
227
+ process.exit(1);
228
+ }
@@ -0,0 +1,21 @@
1
+ export const IGNORED_DIRS = [
2
+ 'node_modules',
3
+ '.git',
4
+ '.next',
5
+ 'dist',
6
+ 'build',
7
+ 'coverage',
8
+ '.venv',
9
+ 'venv',
10
+ '__pycache__',
11
+ '.terraform',
12
+ '.devctx',
13
+ ];
14
+
15
+ export const IGNORED_FILE_NAMES = [
16
+ 'pnpm-lock.yaml',
17
+ 'package-lock.json',
18
+ 'yarn.lock',
19
+ 'bun.lockb',
20
+ 'npm-shrinkwrap.json',
21
+ ];
@@ -1,24 +1,12 @@
1
- /**
2
- * Cross-project context for monorepos and related projects.
3
- *
4
- * Enables context sharing across multiple related codebases.
5
- */
6
-
7
1
  import fs from 'node:fs';
8
2
  import path from 'node:path';
9
3
  import { loadIndex } from './index.js';
10
4
  import { smartSearch } from './tools/smart-search.js';
11
5
  import { smartRead } from './tools/smart-read.js';
12
- import { projectRoot, setProjectRoot } from './utils/paths.js';
6
+ import { projectRoot } from './utils/paths.js';
13
7
 
14
8
  const CROSS_PROJECT_CONFIG_FILE = '.devctx-projects.json';
15
9
 
16
- /**
17
- * Load cross-project configuration.
18
- *
19
- * @param {string} root - Project root
20
- * @returns {object|null} Configuration or null if not found
21
- */
22
10
  export const loadCrossProjectConfig = (root = projectRoot) => {
23
11
  const configPath = path.join(root, CROSS_PROJECT_CONFIG_FILE);
24
12
 
@@ -34,12 +22,6 @@ export const loadCrossProjectConfig = (root = projectRoot) => {
34
22
  }
35
23
  };
36
24
 
37
- /**
38
- * Discover related projects from config.
39
- *
40
- * @param {string} root - Project root
41
- * @returns {Array<object>} Array of { name, path, type, description }
42
- */
43
25
  export const discoverRelatedProjects = (root = projectRoot) => {
44
26
  const config = loadCrossProjectConfig(root);
45
27
  if (!config?.projects) return [];
@@ -68,13 +50,6 @@ export const discoverRelatedProjects = (root = projectRoot) => {
68
50
  return projects;
69
51
  };
70
52
 
71
- /**
72
- * Search across multiple related projects.
73
- *
74
- * @param {string} query - Search query
75
- * @param {object} options - Search options
76
- * @returns {Promise<Array>} Results from all projects
77
- */
78
53
  export const searchAcrossProjects = async (query, options = {}) => {
79
54
  const {
80
55
  root = projectRoot,
@@ -125,18 +100,10 @@ export const searchAcrossProjects = async (query, options = {}) => {
125
100
  return results;
126
101
  };
127
102
 
128
- /**
129
- * Read files from multiple projects.
130
- *
131
- * @param {Array<object>} fileRefs - Array of { project, file, mode }
132
- * @param {string} root - Project root
133
- * @returns {Promise<Array>} Read results
134
- */
135
103
  export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
136
104
  const relatedProjects = discoverRelatedProjects(root);
137
105
  const projectMap = new Map(relatedProjects.map(p => [p.name, p]));
138
106
 
139
- const originalRoot = projectRoot;
140
107
  const results = [];
141
108
 
142
109
  for (const ref of fileRefs) {
@@ -151,11 +118,10 @@ export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
151
118
  }
152
119
 
153
120
  try {
154
- setProjectRoot(project.path);
155
-
156
121
  const readResult = await smartRead({
157
122
  filePath: ref.file,
158
123
  mode: ref.mode || 'outline',
124
+ cwd: project.path,
159
125
  });
160
126
 
161
127
  results.push({
@@ -175,17 +141,9 @@ export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
175
141
  }
176
142
  }
177
143
 
178
- setProjectRoot(originalRoot);
179
144
  return results;
180
145
  };
181
146
 
182
- /**
183
- * Find symbol definitions across projects.
184
- *
185
- * @param {string} symbolName - Symbol to find
186
- * @param {string} root - Project root
187
- * @returns {Promise<Array>} Symbol locations across projects
188
- */
189
147
  export const findSymbolAcrossProjects = async (symbolName, root = projectRoot) => {
190
148
  const relatedProjects = discoverRelatedProjects(root).filter(p => p.hasIndex);
191
149
  const results = [];
@@ -223,12 +181,6 @@ export const findSymbolAcrossProjects = async (symbolName, root = projectRoot) =
223
181
  return results;
224
182
  };
225
183
 
226
- /**
227
- * Get dependency graph across projects.
228
- *
229
- * @param {string} root - Project root
230
- * @returns {object} Cross-project dependency graph
231
- */
232
184
  export const getCrossProjectDependencies = (root = projectRoot) => {
233
185
  const relatedProjects = discoverRelatedProjects(root).filter(p => p.hasIndex);
234
186
  const dependencies = {
@@ -276,12 +228,6 @@ export const getCrossProjectDependencies = (root = projectRoot) => {
276
228
  return dependencies;
277
229
  };
278
230
 
279
- /**
280
- * Get statistics about cross-project usage.
281
- *
282
- * @param {string} root - Project root
283
- * @returns {object} Usage statistics
284
- */
285
231
  export const getCrossProjectStats = (root = projectRoot) => {
286
232
  const relatedProjects = discoverRelatedProjects(root);
287
233
  const deps = getCrossProjectDependencies(root);
@@ -306,12 +252,6 @@ export const getCrossProjectStats = (root = projectRoot) => {
306
252
  return stats;
307
253
  };
308
254
 
309
- /**
310
- * Create a sample cross-project configuration.
311
- *
312
- * @param {string} root - Project root
313
- * @returns {object} Sample configuration
314
- */
315
255
  export const createSampleConfig = (root = projectRoot) => {
316
256
  return {
317
257
  version: '1.0',