context-mcp-server 1.0.7 → 1.0.8
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 +10 -11
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/src/cli.js +64 -52
- package/src/db.js +946 -805
- package/src/guard.js +9 -3
- package/src/migrator.js +124 -0
- package/src/server.js +7 -6
- package/src/templates/AGENTS.md +6 -13
- package/src/templates/CLAUDE.md +6 -12
- package/src/templates/GEMINI.md +6 -13
- package/src/templates/commands/context-resume.md +1 -1
- package/src/templates/commands/save-context.md +1 -1
- package/src/templates/cursor-rules.mdc +3 -3
- package/src/templates/skills/SKILL.md +7 -13
- package/src/templates/windsurf-rules.md +3 -3
- package/src/tools/context.js +3 -5
- package/src/tools/gitTools.js +1 -3
- package/src/tools/plan.js +130 -0
- package/uv.lock +1 -1
- package/src/tools/discussion.js +0 -123
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ This gets worse as projects grow — reading 20 files to answer "what calls this
|
|
|
25
25
|
|
|
26
26
|
## What It Solves
|
|
27
27
|
|
|
28
|
-
- **Persistent memory** — decisions, bugs, notes, and
|
|
29
|
-
- **Shared store** — `~/.context-mcp
|
|
28
|
+
- **Persistent memory** — decisions, bugs, notes, and config saved across sessions, loaded automatically at conversation start
|
|
29
|
+
- **Shared store** — `~/.context-mcp/projects/<name>/` per-project on your machine; all AI tools read and write it
|
|
30
30
|
- **ContextGraph** — build a knowledge graph of your codebase once, answer structural questions in ~500 tokens instead of ~50,000
|
|
31
31
|
|
|
32
32
|
Real measured reduction on this project: **162× fewer tokens**, **99.38% reduction** per conversation.
|
|
@@ -95,10 +95,10 @@ ctx online --port 3200 # different port
|
|
|
95
95
|
Both `ctx` and `context` are aliases for the same CLI.
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
ctx # interactive mode (UI
|
|
98
|
+
ctx # interactive mode (UI)
|
|
99
99
|
|
|
100
100
|
# Context
|
|
101
|
-
ctx list [project] # list entries
|
|
101
|
+
ctx list [project] # list entries by tree: graph / context / summary / plans
|
|
102
102
|
ctx projects # all projects with graph status + recent entries
|
|
103
103
|
ctx search "query" # keyword → semantic fallback search
|
|
104
104
|
ctx add # add entry interactively
|
|
@@ -115,7 +115,6 @@ ctx settings # view and edit config interactively
|
|
|
115
115
|
|
|
116
116
|
# Tools
|
|
117
117
|
ctx benchmark # token savings report (memory + graph)
|
|
118
|
-
ctx discuss [project] # view discussions
|
|
119
118
|
```
|
|
120
119
|
|
|
121
120
|
---
|
|
@@ -136,12 +135,12 @@ Any file or git operation outside that directory is rejected. Applies to all HTT
|
|
|
136
135
|
|
|
137
136
|
### Memory
|
|
138
137
|
|
|
139
|
-
- `context.resume` — loads recent entries,
|
|
140
|
-
- `context.save` — store
|
|
141
|
-
- `context.get` / `context.update` / `context.delete` — full CRUD
|
|
138
|
+
- `context.resume` — loads recent entries, active plans, and graph status; registers `rootPath` for sandboxing
|
|
139
|
+
- `context.save` — store context with 4 types: `decision`, `bug`, `note`, `config`
|
|
140
|
+
- `context.get` / `context.update` / `context.delete` — full CRUD, single or batch
|
|
142
141
|
- `search` — keyword-first, semantic fallback
|
|
143
|
-
- `
|
|
144
|
-
- Auto-deduplication on save; auto-compact at 20 entries
|
|
142
|
+
- `plan` — auto-triggered when AI makes any plan; saves a markdown summary to a `planDir` you specify
|
|
143
|
+
- Auto-deduplication on save; auto-compact at 20 entries → stored in `summary.json`
|
|
145
144
|
|
|
146
145
|
### ContextGraph
|
|
147
146
|
|
|
@@ -153,7 +152,7 @@ Any file or git operation outside that directory is rejected. Applies to all HTT
|
|
|
153
152
|
codegraph_build(path)
|
|
154
153
|
```
|
|
155
154
|
|
|
156
|
-
Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges.
|
|
155
|
+
Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges. Build metadata saved to `~/.context-mcp/projects/<name>/graph.json`.
|
|
157
156
|
|
|
158
157
|
**Step 2 — Query** (instant, forever):
|
|
159
158
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Persistent AI memory + codebase knowledge graph MCP server. Works across Claude Code, Cursor, Gemini CLI, Codex, Windsurf, VS Code Copilot, Claude.ai, and ChatGPT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"mcp": "node src/index.js",
|
|
15
15
|
"mcp-server": "node src/http.js",
|
|
16
16
|
"cli": "node src/cli.js",
|
|
17
|
-
"check": "node --check src/index.js && node --check src/server.js && node --check src/db.js && node --check src/vector.js && node --check src/summarizer.js && node --check src/search.js && node --check src/config.js && node --check src/cli.js && node --check src/http.js && node --check src/tools/context.js && node --check src/tools/search.js && node --check src/tools/
|
|
17
|
+
"check": "node --check src/index.js && node --check src/server.js && node --check src/db.js && node --check src/vector.js && node --check src/summarizer.js && node --check src/search.js && node --check src/config.js && node --check src/cli.js && node --check src/http.js && node --check src/tools/context.js && node --check src/tools/search.js && node --check src/tools/plan.js &&node --check src/tools/errorCheck.js && node --check src/tools/fileTools.js && node --check src/tools/gitTools.js && node --check src/tools/codegraph.js && node --check src/hooks/autoLink.js && node --check src/hooks/autoContext.js",
|
|
18
18
|
"check-mcp": "npm run check",
|
|
19
19
|
"test": "node --test",
|
|
20
20
|
"prepublishOnly": "npm run check"
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codegraph-mcp"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.8"
|
|
8
8
|
description = "Codebase knowledge graph MCP server — AST extraction, graph queries, community detection"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
package/src/cli.js
CHANGED
|
@@ -163,17 +163,20 @@ function cmdList(args) {
|
|
|
163
163
|
|
|
164
164
|
printSection('Context', filterProject ? `project: ${filterProject}` : 'all projects');
|
|
165
165
|
|
|
166
|
-
// Build per-project map
|
|
166
|
+
// Build per-project map, split entries into their three trees
|
|
167
167
|
const projects = {};
|
|
168
|
+
const ensureProj = p => {
|
|
169
|
+
if (!projects[p]) projects[p] = { context: [], summary: [], plans: [] };
|
|
170
|
+
return projects[p];
|
|
171
|
+
};
|
|
168
172
|
for (const entry of entries) {
|
|
169
173
|
const p = entry.project || 'global';
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
const d = ensureProj(p);
|
|
175
|
+
if (entry.type === 'compaction') d.summary.push(entry);
|
|
176
|
+
else d.context.push(entry);
|
|
172
177
|
}
|
|
173
178
|
for (const disc of allDiscussions) {
|
|
174
|
-
|
|
175
|
-
if (!projects[p]) projects[p] = { contexts: [], discussions: [] };
|
|
176
|
-
projects[p].discussions.push(disc);
|
|
179
|
+
ensureProj(disc.project || 'global').plans.push(disc);
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
const projectNames = Object.keys(projects).sort();
|
|
@@ -185,73 +188,82 @@ function cmdList(args) {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
for (const projectName of projectNames) {
|
|
188
|
-
const pData
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const pData = projects[projectName];
|
|
192
|
+
const graphBuild = _graphForProject(allGraphs, projectName);
|
|
193
|
+
const totalEntries = pData.context.length + pData.summary.length;
|
|
194
|
+
const activePlans = pData.plans.filter(p => p.status === 'active').length;
|
|
195
|
+
const sections = [
|
|
196
|
+
graphBuild && 'graph',
|
|
197
|
+
pData.context.length && 'context',
|
|
198
|
+
pData.summary.length && 'summary',
|
|
199
|
+
pData.plans.length && 'plans',
|
|
200
|
+
].filter(Boolean);
|
|
201
|
+
let secIdx = 0;
|
|
202
|
+
|
|
203
|
+
const projReg = projectRegistry.get(projectName);
|
|
195
204
|
const projIdStr = projReg?.id ? faint(' id:' + projReg.id.slice(0, 8)) : '';
|
|
196
|
-
console.log(`\n ${color(C.dblue, '◆')} ${bold(lblue(projectName))}${projIdStr} ${faint(`${
|
|
205
|
+
console.log(`\n ${color(C.dblue, '◆')} ${bold(lblue(projectName))}${projIdStr} ${faint(`${totalEntries} entries · ${pData.plans.length} plans`)}${activePlans ? ` ${warn('● ' + activePlans + ' active')}` : ''}`);
|
|
197
206
|
console.log(` ${color(C.darkgray, '│')}`);
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
const renderEntries = (items, label, secIsLast) => {
|
|
209
|
+
console.log(` ${color(C.darkgray, secIsLast ? '└─' : '├─')} ${muted(label)} ${faint(items.length + ' entries')}`);
|
|
210
|
+
items.forEach((item, i) => {
|
|
211
|
+
const br = i === items.length - 1 ? '└─' : '├─';
|
|
212
|
+
const date = (item.createdAt || '').slice(0, 10);
|
|
213
|
+
const id = item.id.slice(0, 8);
|
|
214
|
+
const tags = safeTags(item.tags);
|
|
215
|
+
const pipe = secIsLast ? ' ' : '│';
|
|
216
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${pill(item.type || 'note')} ${bold(item.title || '(no title)')} ${faint('id:' + id)} ${faint(date)}`);
|
|
217
|
+
if (tags.length) console.log(` ${color(C.darkgray, pipe)} ${faint(tags.map(t => '#' + t).join(' '))}`);
|
|
218
|
+
});
|
|
219
|
+
if (!secIsLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ── Graph (build stats only) ──────────────────────────────────────────────
|
|
223
|
+
if (graphBuild) {
|
|
201
224
|
secIdx++;
|
|
202
|
-
const isLast = secIdx ===
|
|
203
|
-
const builtAt = (
|
|
204
|
-
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${accent('⬡')} ${muted('graph')} ${faint(`${
|
|
225
|
+
const isLast = secIdx === sections.length;
|
|
226
|
+
const builtAt = (graphBuild.builtAt || '').slice(0, 10);
|
|
227
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${accent('⬡')} ${muted('graph')} ${faint(`${graphBuild.nodes}n · ${graphBuild.edges}e · ${graphBuild.communities} clusters · ${builtAt}`)}`);
|
|
205
228
|
if (!isLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
206
229
|
}
|
|
207
230
|
|
|
208
|
-
// ── Context
|
|
209
|
-
if (pData.
|
|
231
|
+
// ── Context ───────────────────────────────────────────────────────────────
|
|
232
|
+
if (pData.context.length) {
|
|
210
233
|
secIdx++;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
renderEntries(pData.context, 'context', secIdx === sections.length);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Summary (compaction digests only — no type labels) ───────────────────
|
|
238
|
+
if (pData.summary.length) {
|
|
239
|
+
secIdx++;
|
|
240
|
+
const isLast = secIdx === sections.length;
|
|
241
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('summary')} ${faint(pData.summary.length + ' compactions')}`);
|
|
242
|
+
pData.summary.forEach((item, i) => {
|
|
243
|
+
const br = i === pData.summary.length - 1 ? '└─' : '├─';
|
|
215
244
|
const date = (item.createdAt || '').slice(0, 10);
|
|
216
|
-
const type = item.type || 'note';
|
|
217
|
-
const id = item.id.slice(0, 8);
|
|
218
|
-
const tags = safeTags(item.tags);
|
|
219
245
|
const pipe = isLast ? ' ' : '│';
|
|
220
|
-
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${
|
|
221
|
-
if (tags.length) console.log(` ${color(C.darkgray, pipe)} ${faint(tags.map(t => '#' + t).join(' '))}`);
|
|
246
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${faint('◎')} ${bold(item.title || '(compaction)')} ${faint(date)}`);
|
|
222
247
|
});
|
|
223
248
|
if (!isLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
224
249
|
}
|
|
225
250
|
|
|
226
|
-
// ──
|
|
227
|
-
if (pData.
|
|
251
|
+
// ── Plans ─────────────────────────────────────────────────────────────────
|
|
252
|
+
if (pData.plans.length) {
|
|
228
253
|
secIdx++;
|
|
229
|
-
const isLast = secIdx ===
|
|
230
|
-
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('
|
|
231
|
-
pData.
|
|
232
|
-
const br
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const pipe = isLast ? ' ' : '│';
|
|
236
|
-
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${warn(disc.status === 'active' ? '●' : '○')} ${bold(disc.name)} ${pill(disc.status, sc)} ${faint(disc.type || 'plan')}${steps}`);
|
|
237
|
-
if (disc.description) console.log(` ${color(C.darkgray, pipe)} ${faint(disc.description)}`);
|
|
254
|
+
const isLast = secIdx === sections.length;
|
|
255
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('plans')} ${faint(pData.plans.length + ' total')}`);
|
|
256
|
+
pData.plans.forEach((plan, i) => {
|
|
257
|
+
const br = i === pData.plans.length - 1 ? '└─' : '├─';
|
|
258
|
+
const pipe = isLast ? ' ' : '│';
|
|
259
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${warn(plan.status === 'active' ? '●' : '○')} ${bold(plan.name)} ${pill(plan.status, plan.status === 'done' ? 'green' : 'tcyan')} ${faint((plan.description || '').slice(0, 60))}`);
|
|
238
260
|
});
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
// Orphan graphs (no matching project)
|
|
243
|
-
const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => _graphForProject([g], p)));
|
|
244
|
-
if (orphanGraphs.length) {
|
|
245
|
-
console.log(`\n ${color(C.dblue, '◇')} ${muted('other graphs')}`);
|
|
246
|
-
for (const g of orphanGraphs) {
|
|
247
|
-
const pathShort = g.path.replace(/\\/g, '/').split('/').slice(-2).join('/');
|
|
248
|
-
console.log(` ${accent('⬡')} ${bold(pathShort)} ${faint(`${g.nodes}n · ${g.edges}e · ${g.communities} clusters`)}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
264
|
console.log('');
|
|
253
265
|
console.log(line());
|
|
254
|
-
console.log(faint(` ${entries.length} entries · ${allDiscussions.length}
|
|
266
|
+
console.log(faint(` ${entries.length} entries · ${allDiscussions.length} plans · ${allGraphs.length} graphs · ${projectNames.length} projects`));
|
|
255
267
|
}
|
|
256
268
|
|
|
257
269
|
// ── Search ────────────────────────────────────────────────────────────────────
|