cortex-mcp 2.7.5 → 2.8.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 +4 -1
- package/dist/server/mcp-handler.js +213 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Next conversation → Cortex injects relevant memories → AI already knows your
|
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
67
|
-
## All
|
|
67
|
+
## All 30 Tools (All Free)
|
|
68
68
|
|
|
69
69
|
### Core Memory
|
|
70
70
|
| Tool | What it does |
|
|
@@ -121,6 +121,9 @@ Next conversation → Cortex injects relevant memories → AI already knows your
|
|
|
121
121
|
| Tool | What it does |
|
|
122
122
|
|------|-------------|
|
|
123
123
|
| `health_check` | Server health, embedding status, vector search status |
|
|
124
|
+
| `repair_brain` | Rebuild FTS index, integrity check, VACUUM, fix orphaned data |
|
|
125
|
+
| `detect_conflicts` | Find contradictory memories (e.g. "use X" vs "avoid X") |
|
|
126
|
+
| `suggest_cleanup` | Identify stale low-value memories you should delete |
|
|
124
127
|
|
|
125
128
|
---
|
|
126
129
|
|
|
@@ -357,6 +357,32 @@ const MCP_TOOLS = [
|
|
|
357
357
|
properties: {},
|
|
358
358
|
},
|
|
359
359
|
},
|
|
360
|
+
{
|
|
361
|
+
name: 'repair_brain',
|
|
362
|
+
description: 'Repair and optimize the memory database. Rebuilds the full-text search index, checks database integrity, removes orphaned vectors, and runs VACUUM. Use this if search stops working or the brain feels slow.',
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: 'object',
|
|
365
|
+
properties: {},
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'detect_conflicts',
|
|
370
|
+
description: 'Scan all active memories for contradictions. Finds pairs of memories that say opposite things (e.g. "use PostgreSQL" vs "use MongoDB"). Returns conflicts so the user can resolve which is correct.',
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: 'object',
|
|
373
|
+
properties: {},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'suggest_cleanup',
|
|
378
|
+
description: 'Identify stale, low-value memories that should be deleted. Finds memories older than 30 days with low confidence, zero access count, and no pins. Returns a list the user can review and bulk-delete.',
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: 'object',
|
|
381
|
+
properties: {
|
|
382
|
+
olderThanDays: { type: 'number', description: 'Only suggest memories older than N days (default 30)' },
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
360
386
|
];
|
|
361
387
|
// --- Dynamic Context via ContextBuilder ---
|
|
362
388
|
let cachedContextBuilder = null;
|
|
@@ -580,6 +606,15 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
580
606
|
else if (toolName === 'analytics') {
|
|
581
607
|
return handleAnalytics(id);
|
|
582
608
|
}
|
|
609
|
+
else if (toolName === 'repair_brain') {
|
|
610
|
+
return handleRepairBrain(id);
|
|
611
|
+
}
|
|
612
|
+
else if (toolName === 'detect_conflicts') {
|
|
613
|
+
return handleDetectConflicts(id);
|
|
614
|
+
}
|
|
615
|
+
else if (toolName === 'suggest_cleanup') {
|
|
616
|
+
return handleSuggestCleanup(id, args);
|
|
617
|
+
}
|
|
583
618
|
else {
|
|
584
619
|
return {
|
|
585
620
|
jsonrpc: '2.0', id,
|
|
@@ -1134,6 +1169,28 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1134
1169
|
for (const s of sessions)
|
|
1135
1170
|
parts.push(s);
|
|
1136
1171
|
}
|
|
1172
|
+
// ─── BRAIN LAYER 3.5: What's New Since Last Session ──────────────
|
|
1173
|
+
try {
|
|
1174
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
1175
|
+
const oneDayAgo = Date.now() - 86400000;
|
|
1176
|
+
const recentNew = db.prepare(
|
|
1177
|
+
'SELECT type, intent FROM memory_units WHERE is_active = 1 AND created_at > ? ORDER BY created_at DESC LIMIT 10'
|
|
1178
|
+
).all(oneDayAgo);
|
|
1179
|
+
if (recentNew.length > 0) {
|
|
1180
|
+
const typeCounts = {};
|
|
1181
|
+
for (const m of recentNew) {
|
|
1182
|
+
typeCounts[m.type] = (typeCounts[m.type] || 0) + 1;
|
|
1183
|
+
}
|
|
1184
|
+
const summary = Object.entries(typeCounts).map(([t, c]) => `${c} ${t.toLowerCase()}${c > 1 ? 's' : ''}`).join(', ');
|
|
1185
|
+
parts.push(`\n## 🆕 Since Last Session\n_${recentNew.length} new memories (${summary})_`);
|
|
1186
|
+
for (const m of recentNew.slice(0, 5)) {
|
|
1187
|
+
parts.push(`- [${m.type}] ${m.intent}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (recentNew.length > 5) {
|
|
1190
|
+
parts.push(`_...and ${recentNew.length - 5} more_`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
} catch { /* non-fatal */ }
|
|
1137
1194
|
// ─── BRAIN LAYER 4: Hot Corrections (learning rate) ──────────────
|
|
1138
1195
|
const hotCorrections = (0, learning_rate_1.formatHotCorrections)(memoryStore);
|
|
1139
1196
|
if (hotCorrections)
|
|
@@ -2025,6 +2082,162 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
2025
2082
|
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `analytics error: ${err.message}` }], isError: true } };
|
|
2026
2083
|
}
|
|
2027
2084
|
}
|
|
2085
|
+
// ═══ NEW CORE TOOLS: repair_brain, detect_conflicts, suggest_cleanup ═══
|
|
2086
|
+
function handleRepairBrain(id) {
|
|
2087
|
+
try {
|
|
2088
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
2089
|
+
const repairs = [];
|
|
2090
|
+
// 1. Integrity check
|
|
2091
|
+
const integrityResult = db.pragma('integrity_check');
|
|
2092
|
+
const integrityOk = integrityResult && integrityResult[0] && integrityResult[0].integrity_check === 'ok';
|
|
2093
|
+
repairs.push(integrityOk ? '✅ Database integrity: OK' : '⚠️ Database integrity issues detected');
|
|
2094
|
+
// 2. Rebuild FTS index
|
|
2095
|
+
try {
|
|
2096
|
+
db.exec("INSERT INTO memory_fts(memory_fts) VALUES('rebuild')");
|
|
2097
|
+
repairs.push('✅ Full-text search index: Rebuilt');
|
|
2098
|
+
} catch (e) {
|
|
2099
|
+
repairs.push('⚠️ FTS rebuild failed: ' + e.message);
|
|
2100
|
+
}
|
|
2101
|
+
// 3. Fix orphaned vectors (vectors for deleted memories)
|
|
2102
|
+
try {
|
|
2103
|
+
const orphaned = db.prepare(
|
|
2104
|
+
"DELETE FROM memory_vectors WHERE memory_id NOT IN (SELECT id FROM memory_units WHERE is_active = 1)"
|
|
2105
|
+
).run();
|
|
2106
|
+
repairs.push(`✅ Orphaned vectors: Cleaned ${orphaned.changes} entries`);
|
|
2107
|
+
} catch { repairs.push('✅ Orphaned vectors: None (table may not exist)'); }
|
|
2108
|
+
// 4. Fix memories with missing created_at
|
|
2109
|
+
try {
|
|
2110
|
+
const fixed = db.prepare(
|
|
2111
|
+
"UPDATE memory_units SET created_at = timestamp WHERE created_at = 0 OR created_at IS NULL"
|
|
2112
|
+
).run();
|
|
2113
|
+
if (fixed.changes > 0) {
|
|
2114
|
+
repairs.push(`✅ Fixed ${fixed.changes} memories with missing created_at`);
|
|
2115
|
+
}
|
|
2116
|
+
} catch { }
|
|
2117
|
+
// 5. WAL checkpoint + VACUUM
|
|
2118
|
+
try {
|
|
2119
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
2120
|
+
db.exec('VACUUM');
|
|
2121
|
+
repairs.push('✅ Database compacted (VACUUM)');
|
|
2122
|
+
} catch (e) {
|
|
2123
|
+
repairs.push('⚠️ VACUUM failed: ' + e.message);
|
|
2124
|
+
}
|
|
2125
|
+
// 6. Stats after repair
|
|
2126
|
+
const activeCount = memoryStore.activeCount();
|
|
2127
|
+
repairs.push(`\n📊 Active memories: ${activeCount}`);
|
|
2128
|
+
(0, memory_cache_1.invalidateCache)();
|
|
2129
|
+
return {
|
|
2130
|
+
jsonrpc: '2.0', id,
|
|
2131
|
+
result: { content: [{ type: 'text', text: `# 🔧 Brain Repair Complete\n\n${repairs.join('\n')}` }] },
|
|
2132
|
+
};
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Repair error: ${err.message}` }], isError: true } };
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function handleDetectConflicts(id) {
|
|
2138
|
+
try {
|
|
2139
|
+
const memories = memoryStore.getActive(500);
|
|
2140
|
+
// Only look at decisions and conventions — these are the ones that conflict
|
|
2141
|
+
const candidates = memories.filter(m =>
|
|
2142
|
+
['DECISION', 'CONVENTION', 'CORRECTION'].includes(m.type)
|
|
2143
|
+
);
|
|
2144
|
+
const conflicts = [];
|
|
2145
|
+
// Compare each pair for contradictions
|
|
2146
|
+
const OPPOSITE_PAIRS = [
|
|
2147
|
+
['always', 'never'], ['use', 'avoid'], ['enable', 'disable'],
|
|
2148
|
+
['allow', 'block'], ['include', 'exclude'], ['add', 'remove'],
|
|
2149
|
+
['yes', 'no'], ['true', 'false'], ['must', 'must not'],
|
|
2150
|
+
['do', "don't"], ['do', 'do not'], ['can', 'cannot'],
|
|
2151
|
+
];
|
|
2152
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
2153
|
+
for (let j = i + 1; j < candidates.length; j++) {
|
|
2154
|
+
const a = candidates[i];
|
|
2155
|
+
const b = candidates[j];
|
|
2156
|
+
const aText = (a.intent + ' ' + (a.action || '')).toLowerCase();
|
|
2157
|
+
const bText = (b.intent + ' ' + (b.action || '')).toLowerCase();
|
|
2158
|
+
// Check if they share a subject but have opposite verbs
|
|
2159
|
+
let isConflict = false;
|
|
2160
|
+
for (const [pos, neg] of OPPOSITE_PAIRS) {
|
|
2161
|
+
if ((aText.includes(pos) && bText.includes(neg)) ||
|
|
2162
|
+
(aText.includes(neg) && bText.includes(pos))) {
|
|
2163
|
+
// Check they share at least one significant word (3+ chars)
|
|
2164
|
+
const aWords = aText.split(/\s+/).filter(w => w.length > 3);
|
|
2165
|
+
const bWords = new Set(bText.split(/\s+/).filter(w => w.length > 3));
|
|
2166
|
+
const shared = aWords.filter(w => bWords.has(w));
|
|
2167
|
+
if (shared.length >= 1) {
|
|
2168
|
+
isConflict = true;
|
|
2169
|
+
break;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
if (isConflict) {
|
|
2174
|
+
conflicts.push({
|
|
2175
|
+
a: { id: a.id, type: a.type, intent: a.intent, age: Math.floor((Date.now() - a.createdAt) / 86400000) },
|
|
2176
|
+
b: { id: b.id, type: b.type, intent: b.intent, age: Math.floor((Date.now() - b.createdAt) / 86400000) },
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (conflicts.length === 0) {
|
|
2182
|
+
return {
|
|
2183
|
+
jsonrpc: '2.0', id,
|
|
2184
|
+
result: { content: [{ type: 'text', text: '✅ No conflicts detected! All memories are consistent.' }] },
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
const lines = [`# ⚠️ ${conflicts.length} Potential Conflict(s) Detected\n`];
|
|
2188
|
+
for (let i = 0; i < conflicts.length; i++) {
|
|
2189
|
+
const c = conflicts[i];
|
|
2190
|
+
lines.push(`### Conflict ${i + 1}`);
|
|
2191
|
+
lines.push(`**A:** [${c.a.type}] ${c.a.intent} _(${c.a.age}d old, id: ${c.a.id})_`);
|
|
2192
|
+
lines.push(`**B:** [${c.b.type}] ${c.b.intent} _(${c.b.age}d old, id: ${c.b.id})_`);
|
|
2193
|
+
lines.push(`→ Use \`delete_memory\` to remove the outdated one.\n`);
|
|
2194
|
+
}
|
|
2195
|
+
return {
|
|
2196
|
+
jsonrpc: '2.0', id,
|
|
2197
|
+
result: { content: [{ type: 'text', text: lines.join('\n') }] },
|
|
2198
|
+
};
|
|
2199
|
+
} catch (err) {
|
|
2200
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `conflict detection error: ${err.message}` }], isError: true } };
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
function handleSuggestCleanup(id, args) {
|
|
2204
|
+
try {
|
|
2205
|
+
const olderThanDays = args.olderThanDays || 30;
|
|
2206
|
+
const cutoff = Date.now() - (olderThanDays * 86400000);
|
|
2207
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
2208
|
+
// Find stale, low-value memories
|
|
2209
|
+
const stale = db.prepare(`
|
|
2210
|
+
SELECT * FROM memory_units
|
|
2211
|
+
WHERE is_active = 1
|
|
2212
|
+
AND created_at < ?
|
|
2213
|
+
AND confidence < 0.4
|
|
2214
|
+
AND access_count <= 1
|
|
2215
|
+
AND (tags IS NULL OR tags NOT LIKE '%pinned%')
|
|
2216
|
+
ORDER BY confidence ASC, access_count ASC
|
|
2217
|
+
LIMIT 25
|
|
2218
|
+
`).all(cutoff);
|
|
2219
|
+
if (stale.length === 0) {
|
|
2220
|
+
return {
|
|
2221
|
+
jsonrpc: '2.0', id,
|
|
2222
|
+
result: { content: [{ type: 'text', text: `✅ Brain is clean! No stale memories older than ${olderThanDays} days with low value found.` }] },
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const lines = [`# 🧹 ${stale.length} Stale Memories (Cleanup Suggestions)\n`];
|
|
2226
|
+
lines.push(`These memories are older than ${olderThanDays} days, have low confidence, and are rarely accessed:\n`);
|
|
2227
|
+
for (const row of stale) {
|
|
2228
|
+
const age = Math.floor((Date.now() - row.created_at) / 86400000);
|
|
2229
|
+
lines.push(`- **[${row.type}]** ${row.intent}`);
|
|
2230
|
+
lines.push(` _Confidence: ${(row.confidence * 100).toFixed(0)}% · Accessed: ${row.access_count}x · Age: ${age}d · id: ${row.id}_`);
|
|
2231
|
+
}
|
|
2232
|
+
lines.push(`\n→ Use \`delete_memory\` with each id to remove, or \`clear_memories\` for bulk cleanup.`);
|
|
2233
|
+
return {
|
|
2234
|
+
jsonrpc: '2.0', id,
|
|
2235
|
+
result: { content: [{ type: 'text', text: lines.join('\n') }] },
|
|
2236
|
+
};
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `cleanup suggestion error: ${err.message}` }], isError: true } };
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2028
2241
|
return { handleMCPRequest };
|
|
2029
2242
|
}
|
|
2030
2243
|
// ─── NEW: Export/Import/Graph Handlers ─────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "cortex-mcp",
|
|
3
3
|
"displayName": "Cortex MCP Server",
|
|
4
4
|
"description": "Persistent memory for AI coding assistants. Injects context from past sessions into every LLM request.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.8.0",
|
|
6
6
|
"publisher": "cortex",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"engines": {
|