context-mcp-server 1.0.4 → 1.0.6
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 +23 -10
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.js +277 -61
- package/src/db.js +17 -5
- package/src/hooks/autoContext.js +1 -1
- package/src/templates/AGENTS.md +29 -11
- package/src/templates/CLAUDE.md +39 -12
- package/src/templates/GEMINI.md +29 -11
- package/src/templates/commands/context-resume.md +10 -0
- package/src/templates/commands/graph-build.md +5 -0
- package/src/templates/commands/save-context.md +9 -0
- package/src/templates/skills/SKILL.md +115 -0
- package/src/tools/codegraph.js +4 -1
- package/src/tools/context.js +9 -3
- package/src/tools/gitTools.js +3 -4
- package/uv.lock +1 -1
package/README.md
CHANGED
|
@@ -371,7 +371,10 @@ Available to web clients (Claude.ai, ChatGPT) only — local AI clients use thei
|
|
|
371
371
|
|
|
372
372
|
All file and git operations are sandboxed to the registered project root. Enable git tools with `--access-git` or `access_git: true` in config.
|
|
373
373
|
|
|
374
|
-
### CodeGraph
|
|
374
|
+
### ContextGraph (CodeGraph)
|
|
375
|
+
|
|
376
|
+
> Also referred to as **ContextGraph** — the MCP tools use the `codegraph_*` prefix but both names mean the same thing.
|
|
377
|
+
|
|
375
378
|
- `codegraph_build` — AST scan using tree-sitter: functions, classes, imports, edges. Runs locally, no API cost.
|
|
376
379
|
- `codegraph_query` — fetch any details about the codebase using natural language: find functions, classes, files, dependencies, callers
|
|
377
380
|
- `codegraph_explain` — single node: type, file location, all direct connections (depends_on, used_by)
|
|
@@ -381,15 +384,25 @@ All file and git operations are sandboxed to the registered project root. Enable
|
|
|
381
384
|
|
|
382
385
|
### Multi-AI Support
|
|
383
386
|
|
|
384
|
-
| AI | Config File |
|
|
385
|
-
|
|
386
|
-
| Claude Code | `.claude/mcp.json` |
|
|
387
|
-
|
|
|
388
|
-
|
|
|
389
|
-
| Gemini CLI | `.gemini/settings.json` | `GEMINI.md` |
|
|
390
|
-
| Codex CLI | `.codex/config.toml` | `AGENTS.md` |
|
|
391
|
-
|
|
|
392
|
-
| Claude.ai / ChatGPT | HTTP (`ctx online`) | — |
|
|
387
|
+
| AI | Config File | Instructions | Slash Commands |
|
|
388
|
+
|----|------------|--------------|----------------|
|
|
389
|
+
| Claude Code | `.claude/mcp.json` | Skill → `~/.claude/skills/context-mcp/` (global) | ✓ (3 commands) |
|
|
390
|
+
| Cursor | `.cursor/mcp.json` | Rule → `.cursor/rules/context-mcp.mdc` | — |
|
|
391
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | Rule → `.windsurf/rules/context-mcp.md` | — |
|
|
392
|
+
| Gemini CLI | `.gemini/settings.json` | `GEMINI.md` | — |
|
|
393
|
+
| Codex CLI | `.codex/config.toml` | `AGENTS.md` | — |
|
|
394
|
+
| VS Code Copilot | `.vscode/mcp.json` | — | — |
|
|
395
|
+
| Claude.ai / ChatGPT | HTTP (`ctx online`) | — | — |
|
|
396
|
+
|
|
397
|
+
> Claude Code installs context-mcp as a **skill** (`~/.claude/skills/context-mcp/SKILL.md`) — available globally across all projects, not just the current one. Cursor and Windsurf use their native rules system. Gemini and Codex use plain instruction files since they have no skill/rules system.
|
|
398
|
+
|
|
399
|
+
`ctx install --claude` also writes slash commands into `.claude/commands/`:
|
|
400
|
+
|
|
401
|
+
| Command | What it does |
|
|
402
|
+
|---------|-------------|
|
|
403
|
+
| `/context-resume` | Resume context for the current project |
|
|
404
|
+
| `/graph-build` | Build or rebuild the ContextGraph |
|
|
405
|
+
| `/save-context` | Save a note, decision, or bug to context |
|
|
393
406
|
|
|
394
407
|
> The context store lives at `~/.context-mcp/` — not inside any tool, IDE, or session. A decision saved in Claude Code is visible in Cursor. A bug logged from Gemini CLI shows up when you resume in Codex.
|
|
395
408
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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": {
|
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.6"
|
|
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
|
@@ -106,28 +106,46 @@ function printSection(title, meta = '') {
|
|
|
106
106
|
|
|
107
107
|
function printUsage() {
|
|
108
108
|
printBanner();
|
|
109
|
-
|
|
109
|
+
|
|
110
|
+
// Terminal commands (ctx ...)
|
|
111
|
+
printSection('Terminal commands', 'run from your shell');
|
|
110
112
|
const cmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
111
|
-
cmd('ctx',
|
|
112
|
-
cmd('ctx list [project]',
|
|
113
|
-
cmd('ctx search <query>',
|
|
114
|
-
cmd('ctx add',
|
|
115
|
-
cmd('ctx delete <id-prefix>',
|
|
116
|
-
cmd('ctx delete project <name|id>',
|
|
117
|
-
cmd('ctx summary [project]',
|
|
118
|
-
cmd('ctx projects',
|
|
119
|
-
cmd('ctx discuss [project]',
|
|
120
|
-
cmd('ctx benchmark',
|
|
113
|
+
cmd('ctx', 'open interactive mode');
|
|
114
|
+
cmd('ctx list [project]', 'list entries + discussions + graphs');
|
|
115
|
+
cmd('ctx search <query>', 'keyword → semantic fallback search');
|
|
116
|
+
cmd('ctx add', 'add entry interactively');
|
|
117
|
+
cmd('ctx delete <id-prefix>', 'delete one entry');
|
|
118
|
+
cmd('ctx delete project <name|id>', 'delete all entries for a project');
|
|
119
|
+
cmd('ctx summary [project]', 'summarize recent entries');
|
|
120
|
+
cmd('ctx projects', 'show all projects + graphs');
|
|
121
|
+
cmd('ctx discuss [project]', 'show discussions');
|
|
122
|
+
cmd('ctx benchmark', 'token savings report (memory + graph)');
|
|
121
123
|
console.log('');
|
|
122
|
-
cmd('ctx install --initial',
|
|
123
|
-
cmd('ctx install --<platform>', 'write MCP config +
|
|
124
|
+
cmd('ctx install --initial', 'install / update Node.js + Python (codegraph) deps');
|
|
125
|
+
cmd('ctx install --<platform>', 'write MCP config + skill/rules for an AI platform');
|
|
124
126
|
cmd('ctx install --all', 'install for all platforms at once');
|
|
125
|
-
cmd('ctx online [--port N]', 'start HTTP server
|
|
127
|
+
cmd('ctx online [--port N]', 'start HTTP server for Claude.ai / ChatGPT');
|
|
126
128
|
cmd('ctx online --close', 'stop the running HTTP server');
|
|
127
129
|
cmd('ctx settings', 'view and edit config (port, host, client id/secret)');
|
|
128
|
-
|
|
130
|
+
cmd('ctx update', 'check for and apply latest version');
|
|
129
131
|
cmd('ctx help', 'show this screen');
|
|
130
132
|
console.log('');
|
|
133
|
+
|
|
134
|
+
// Interactive mode commands (no ctx prefix)
|
|
135
|
+
printSection('Interactive mode', 'type these inside ctx (no "ctx" prefix)');
|
|
136
|
+
const icmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
137
|
+
icmd('list [project]', 'list entries');
|
|
138
|
+
icmd('search <query>', 'search context');
|
|
139
|
+
icmd('add', 'add entry');
|
|
140
|
+
icmd('projects', 'show all projects');
|
|
141
|
+
icmd('discuss [project]', 'show discussions');
|
|
142
|
+
icmd('summary [project]', 'summarize recent entries');
|
|
143
|
+
icmd('benchmark', 'token savings report');
|
|
144
|
+
icmd('install --<platform>', 'install for a platform');
|
|
145
|
+
icmd('settings', 'edit config');
|
|
146
|
+
icmd('clear', 'clear screen');
|
|
147
|
+
icmd('exit / quit / q', 'exit interactive mode');
|
|
148
|
+
console.log('');
|
|
131
149
|
}
|
|
132
150
|
function clearScreen() {
|
|
133
151
|
// \x1b[2J = clear screen, \x1b[3J = clear scrollback, \x1b[H = cursor home
|
|
@@ -168,7 +186,7 @@ function cmdList(args) {
|
|
|
168
186
|
|
|
169
187
|
for (const projectName of projectNames) {
|
|
170
188
|
const pData = projects[projectName];
|
|
171
|
-
const graph = allGraphs
|
|
189
|
+
const graph = _graphForProject(allGraphs, projectName);
|
|
172
190
|
const activeD = pData.discussions.filter(d => d.status === 'active').length;
|
|
173
191
|
const totalSecs = (pData.contexts.length > 0 ? 1 : 0) + (pData.discussions.length > 0 ? 1 : 0) + (graph ? 1 : 0);
|
|
174
192
|
let secIdx = 0;
|
|
@@ -222,7 +240,7 @@ function cmdList(args) {
|
|
|
222
240
|
}
|
|
223
241
|
|
|
224
242
|
// Orphan graphs (no matching project)
|
|
225
|
-
const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => g
|
|
243
|
+
const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => _graphForProject([g], p)));
|
|
226
244
|
if (orphanGraphs.length) {
|
|
227
245
|
console.log(`\n ${color(C.dblue, '◇')} ${muted('other graphs')}`);
|
|
228
246
|
for (const g of orphanGraphs) {
|
|
@@ -274,7 +292,7 @@ function cmdProjects() {
|
|
|
274
292
|
const entries = getContext({ project: project.name, limit: 3, compact: true }).filter(e => e.status !== 'archived');
|
|
275
293
|
const discs = allDiscs.filter(d => (d.project || 'global') === project.name);
|
|
276
294
|
const activeD = discs.filter(d => d.status === 'active');
|
|
277
|
-
const graph = graphs
|
|
295
|
+
const graph = _graphForProject(graphs, project.name);
|
|
278
296
|
|
|
279
297
|
const barLen = Math.min(Math.ceil(project.count / 2), 24);
|
|
280
298
|
const bar = color(C.dblue, '█'.repeat(barLen)) + color(C.darkgray, '░'.repeat(24 - barLen));
|
|
@@ -531,65 +549,187 @@ function _writeFile(filePath, content, label) {
|
|
|
531
549
|
console.log(` ${ok('✓')} ${label.padEnd(28)} ${faint(filePath.replace(/\\/g, '/'))}`);
|
|
532
550
|
}
|
|
533
551
|
|
|
552
|
+
// Entries ctx install writes into project roots — add to user's global gitignore if one exists
|
|
553
|
+
const _GLOBAL_GITIGNORE_ENTRIES = [
|
|
554
|
+
// Installed instruction files
|
|
555
|
+
'CLAUDE.md',
|
|
556
|
+
'GEMINI.md',
|
|
557
|
+
'AGENTS.md',
|
|
558
|
+
// AI/IDE platform config folders (context-mcp specific — safe to ignore globally)
|
|
559
|
+
'.claude/',
|
|
560
|
+
'.cursor/',
|
|
561
|
+
'.vscode/',
|
|
562
|
+
'.gemini/',
|
|
563
|
+
'.codex/',
|
|
564
|
+
'.windsurf/',
|
|
565
|
+
// Build outputs and session artifacts
|
|
566
|
+
'codegraph-cache/',
|
|
567
|
+
'.mcp.json',
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
// Match a graph to a project by exact last-path-component comparison (not substring)
|
|
571
|
+
function _graphForProject(graphs, projectName) {
|
|
572
|
+
const norm = p => (p || '').toLowerCase().replace(/\\/g, '/').replace(/\/$/, '');
|
|
573
|
+
const name = projectName.toLowerCase();
|
|
574
|
+
return graphs.find(g => norm(g.path).split('/').pop() === name) || null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const _PROJECT_GITIGNORE_ENTRIES = [
|
|
578
|
+
'.claude/', '.cursor/', '.vscode/', '.gemini/', '.codex/',
|
|
579
|
+
'codegraph-cache/', '.mcp.json', 'CLAUDE.md', 'GEMINI.md', 'AGENTS.md',
|
|
580
|
+
];
|
|
581
|
+
|
|
582
|
+
function _updateProjectGitignore(projectDir) {
|
|
583
|
+
const giPath = join(projectDir, '.gitignore');
|
|
584
|
+
const existing = existsSync(giPath) ? readFileSync(giPath, 'utf8') : '';
|
|
585
|
+
const lines = existing.split(/\r?\n/);
|
|
586
|
+
const missing = _PROJECT_GITIGNORE_ENTRIES.filter(e => !lines.includes(e));
|
|
587
|
+
if (!missing.length) return;
|
|
588
|
+
const block = '\n# context-mcp — written by ctx install\n' + missing.join('\n') + '\n';
|
|
589
|
+
writeFileSync(giPath, (existing ? existing.trimEnd() : '') + block, 'utf8');
|
|
590
|
+
console.log(` ${ok('✓')} ${'project .gitignore'.padEnd(28)} ${faint(giPath.replace(/\\/g, '/'))}`);
|
|
591
|
+
for (const e of missing) console.log(` ${faint('+ ' + e)}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function _updateGlobalGitignore() {
|
|
595
|
+
// Resolve global gitignore path: git config > ~/.gitignore_global > ~/.gitignore
|
|
596
|
+
let giPath = null;
|
|
597
|
+
const gitCfg = spawnSync('git', ['config', '--global', 'core.excludesFile'], { encoding: 'utf8' });
|
|
598
|
+
if (gitCfg.status === 0 && gitCfg.stdout.trim()) {
|
|
599
|
+
const resolved = gitCfg.stdout.trim().replace(/^~/, homedir());
|
|
600
|
+
if (existsSync(resolved)) giPath = resolved;
|
|
601
|
+
}
|
|
602
|
+
if (!giPath) {
|
|
603
|
+
for (const candidate of [join(homedir(), '.gitignore_global'), join(homedir(), '.gitignore')]) {
|
|
604
|
+
if (existsSync(candidate)) { giPath = candidate; break; }
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (!giPath) return; // no global gitignore — skip silently
|
|
608
|
+
|
|
609
|
+
const existing = readFileSync(giPath, 'utf8');
|
|
610
|
+
const lines = existing.split(/\r?\n/);
|
|
611
|
+
const missing = _GLOBAL_GITIGNORE_ENTRIES.filter(e => !lines.includes(e));
|
|
612
|
+
if (!missing.length) return;
|
|
613
|
+
|
|
614
|
+
const block = '\n# context-mcp — written by ctx install\n' + missing.join('\n') + '\n';
|
|
615
|
+
writeFileSync(giPath, existing.trimEnd() + block, 'utf8');
|
|
616
|
+
console.log(` ${ok('✓')} ${'global gitignore'.padEnd(28)} ${faint(giPath.replace(/\\/g, '/'))}`);
|
|
617
|
+
for (const e of missing) console.log(` ${faint('+ ' + e)}`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function _writeCommands(baseDir) {
|
|
621
|
+
const cmdsDir = join(TPLS, 'commands');
|
|
622
|
+
const destDir = join(baseDir, '.claude', 'commands');
|
|
623
|
+
for (const [name, label] of [
|
|
624
|
+
['context-resume.md', '/context-resume'],
|
|
625
|
+
['graph-build.md', '/graph-build'],
|
|
626
|
+
['save-context.md', '/save-context'],
|
|
627
|
+
]) {
|
|
628
|
+
const src = join(cmdsDir, name);
|
|
629
|
+
if (existsSync(src)) {
|
|
630
|
+
_writeFile(join(destDir, name), readFileSync(src, 'utf8'), label);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const MCP_SERVER_CMD = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
|
|
636
|
+
|
|
534
637
|
const PLATFORMS = {
|
|
535
638
|
claude: {
|
|
536
639
|
label: 'Claude Code',
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
640
|
+
restartNote: 'Type /context-resume in Claude Code to start using context-mcp.',
|
|
641
|
+
install(dir, scope) {
|
|
642
|
+
// Install as a skill (global, works across all projects) instead of a flat CLAUDE.md
|
|
643
|
+
const skillSrc = join(TPLS, 'skills', 'SKILL.md');
|
|
644
|
+
const skillDest = join(homedir(), '.claude', 'skills', 'context-mcp', 'SKILL.md');
|
|
645
|
+
if (existsSync(skillSrc)) {
|
|
646
|
+
_writeFile(skillDest, readFileSync(skillSrc, 'utf8'), '~/.claude/skills/context-mcp/');
|
|
647
|
+
}
|
|
648
|
+
// Slash commands are always user-global (not project-scoped)
|
|
649
|
+
_writeCommands(homedir());
|
|
650
|
+
// Register via claude CLI so the server is trusted immediately (no manual trust prompt)
|
|
651
|
+
const scopeFlag = scope === 'global' ? 'user' : 'project';
|
|
652
|
+
const reg = spawnSync(
|
|
653
|
+
'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
654
|
+
{ encoding: 'utf8', shell: true },
|
|
655
|
+
);
|
|
656
|
+
if (reg.status === 0) {
|
|
657
|
+
console.log(` ${ok('✓')} ${'registered via claude mcp add'.padEnd(28)} ${faint('scope: ' + scopeFlag)}`);
|
|
658
|
+
} else {
|
|
659
|
+
console.log(` ${faint('ℹ')} claude CLI not found — open Claude Code and trust context-mcp when prompted`);
|
|
660
|
+
}
|
|
544
661
|
},
|
|
545
662
|
},
|
|
546
663
|
cursor: {
|
|
547
664
|
label: 'Cursor',
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
665
|
+
restartNote: 'Restart Cursor to load the new MCP server.',
|
|
666
|
+
install(dir, scope) {
|
|
667
|
+
const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
668
|
+
_writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
|
|
669
|
+
if (scope === 'project') {
|
|
670
|
+
const mdc = _tpl('cursor-rules.mdc');
|
|
671
|
+
if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
|
|
672
|
+
}
|
|
673
|
+
// Try to enable via cursor CLI (enable/disable only — no add command)
|
|
674
|
+
const reg = spawnSync(
|
|
675
|
+
'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
|
|
676
|
+
{ encoding: 'utf8', shell: true },
|
|
677
|
+
);
|
|
678
|
+
if (reg.status === 0) {
|
|
679
|
+
console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
|
|
680
|
+
}
|
|
555
681
|
},
|
|
556
682
|
},
|
|
557
683
|
vscode: {
|
|
558
684
|
label: 'VS Code Copilot',
|
|
559
|
-
|
|
685
|
+
restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
|
|
686
|
+
install(dir) {
|
|
560
687
|
const mcpJson = JSON.stringify({
|
|
561
|
-
servers: { 'context-mcp': { type: 'stdio',
|
|
688
|
+
servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
|
|
562
689
|
}, null, 2);
|
|
563
|
-
_writeFile(join(
|
|
564
|
-
const md = _tpl('CLAUDE.md');
|
|
565
|
-
if (md) _writeFile(join(cwd, 'CLAUDE.md'), md, 'CLAUDE.md');
|
|
690
|
+
_writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
|
|
566
691
|
},
|
|
567
692
|
},
|
|
568
693
|
gemini: {
|
|
569
694
|
label: 'Gemini CLI',
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
695
|
+
restartNote: 'Restart your Gemini CLI session.',
|
|
696
|
+
install(dir, scope) {
|
|
697
|
+
const cfg = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
698
|
+
_writeFile(join(dir, '.gemini', 'settings.json'), cfg, '.gemini/settings.json');
|
|
699
|
+
if (scope === 'project') {
|
|
700
|
+
const md = _tpl('GEMINI.md');
|
|
701
|
+
if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
|
|
702
|
+
}
|
|
577
703
|
},
|
|
578
704
|
},
|
|
579
705
|
codex: {
|
|
580
706
|
label: 'Codex CLI',
|
|
581
|
-
|
|
707
|
+
restartNote: 'Restart your Codex CLI session.',
|
|
708
|
+
install(dir, scope) {
|
|
582
709
|
const toml = `[[mcp_servers]]\nname = "context-mcp"\ncommand = "npx"\nargs = ["-y", "context-mcp-server@latest"]\n`;
|
|
583
|
-
_writeFile(join(
|
|
584
|
-
|
|
585
|
-
|
|
710
|
+
_writeFile(join(dir, '.codex', 'config.toml'), toml, '.codex/config.toml');
|
|
711
|
+
if (scope === 'project') {
|
|
712
|
+
const md = _tpl('AGENTS.md');
|
|
713
|
+
if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
|
|
714
|
+
}
|
|
715
|
+
// Register via codex CLI so server is active immediately
|
|
716
|
+
const reg = spawnSync(
|
|
717
|
+
'codex', ['mcp', 'add', 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
718
|
+
{ encoding: 'utf8', shell: true },
|
|
719
|
+
);
|
|
720
|
+
if (reg.status === 0) {
|
|
721
|
+
console.log(` ${ok('✓')} ${'registered via codex mcp add'.padEnd(28)}`);
|
|
722
|
+
} else {
|
|
723
|
+
console.log(` ${faint('ℹ')} codex CLI not found — server will load on next Codex session`);
|
|
724
|
+
}
|
|
586
725
|
},
|
|
587
726
|
},
|
|
588
727
|
windsurf: {
|
|
589
728
|
label: 'Windsurf',
|
|
590
|
-
|
|
729
|
+
restartNote: 'Restart Windsurf to load the updated MCP config.',
|
|
730
|
+
install(dir) {
|
|
591
731
|
const rules = _tpl('windsurf-rules.md');
|
|
592
|
-
if (rules) _writeFile(join(
|
|
732
|
+
if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
|
|
593
733
|
const globalCfgPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
594
734
|
let existing = {};
|
|
595
735
|
try { existing = JSON.parse(readFileSync(globalCfgPath, 'utf8')); } catch {}
|
|
@@ -600,7 +740,7 @@ const PLATFORMS = {
|
|
|
600
740
|
},
|
|
601
741
|
};
|
|
602
742
|
|
|
603
|
-
function cmdInstall(args) {
|
|
743
|
+
async function cmdInstall(args) {
|
|
604
744
|
const flags = new Set(args.map(a => a.replace(/^--/, '')));
|
|
605
745
|
const all = flags.has('all');
|
|
606
746
|
const initial = flags.has('initial');
|
|
@@ -665,22 +805,59 @@ function cmdInstall(args) {
|
|
|
665
805
|
return;
|
|
666
806
|
}
|
|
667
807
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
808
|
+
// ── Scope prompt (skip for windsurf-only installs — it's always global) ──────
|
|
809
|
+
const nonWindsurf = keys.filter(k => k !== 'windsurf');
|
|
810
|
+
let scope = 'project';
|
|
811
|
+
let baseDir = process.cwd();
|
|
812
|
+
|
|
813
|
+
if (nonWindsurf.length > 0) {
|
|
814
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
815
|
+
const ask = q => new Promise(resolve => rl.question(` ${accent('›')} ${muted(q)} `, resolve));
|
|
816
|
+
|
|
817
|
+
printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
|
|
818
|
+
console.log('');
|
|
819
|
+
console.log(` ${muted('Install scope:')}`);
|
|
820
|
+
console.log(` ${accent('1.')} For this project ${faint('(writes config into current directory)')}`);
|
|
821
|
+
console.log(` ${accent('2.')} Globally ${faint('(writes config to your home directory)')}`);
|
|
822
|
+
console.log('');
|
|
823
|
+
const answer = (await ask('Choose (1/2) [1]:')).trim();
|
|
824
|
+
rl.close();
|
|
825
|
+
console.log('');
|
|
826
|
+
|
|
827
|
+
if (answer === '2') {
|
|
828
|
+
scope = 'global';
|
|
829
|
+
baseDir = homedir();
|
|
830
|
+
}
|
|
831
|
+
} else {
|
|
832
|
+
printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
|
|
833
|
+
console.log('');
|
|
834
|
+
}
|
|
671
835
|
|
|
672
836
|
for (const key of keys) {
|
|
673
|
-
|
|
837
|
+
const platform = PLATFORMS[key];
|
|
838
|
+
console.log(` ${bold(lblue(platform.label))}`);
|
|
839
|
+
const dir = key === 'windsurf' ? process.cwd() : baseDir;
|
|
674
840
|
try {
|
|
675
|
-
|
|
841
|
+
platform.install(dir, scope);
|
|
676
842
|
} catch (err) {
|
|
677
843
|
console.log(` ${bad('✗')} failed: ${err.message}`);
|
|
678
844
|
}
|
|
845
|
+
if (platform.restartNote) {
|
|
846
|
+
console.log(` ${faint('→ ' + platform.restartNote)}`);
|
|
847
|
+
}
|
|
679
848
|
console.log('');
|
|
680
849
|
}
|
|
681
850
|
|
|
851
|
+
const destLabel = scope === 'global' ? homedir().replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/');
|
|
682
852
|
console.log(line());
|
|
683
|
-
console.log(faint(` ${keys.length} platform(s) installed
|
|
853
|
+
console.log(faint(` ${keys.length} platform(s) installed · scope: ${scope} · ${destLabel}`));
|
|
854
|
+
console.log('');
|
|
855
|
+
|
|
856
|
+
// ── Project .gitignore — add context-mcp entries for this project ──────────
|
|
857
|
+
_updateProjectGitignore(process.cwd());
|
|
858
|
+
|
|
859
|
+
// ── Global gitignore — add context-mcp runtime files if global gitignore exists ──
|
|
860
|
+
_updateGlobalGitignore();
|
|
684
861
|
console.log('');
|
|
685
862
|
|
|
686
863
|
// ── Python / uv setup (codegraph) ─────────────────────────────────────────
|
|
@@ -853,10 +1030,10 @@ async function cmdSettings(existingRl) {
|
|
|
853
1030
|
console.log('');
|
|
854
1031
|
|
|
855
1032
|
const choice = (await ask('Edit field (1-' + FIELDS.length + '):')).trim();
|
|
856
|
-
if (!existingRl) rl.close();
|
|
857
1033
|
|
|
858
1034
|
const idx = parseInt(choice) - 1;
|
|
859
1035
|
if (isNaN(idx) || idx < 0 || idx >= FIELDS.length) {
|
|
1036
|
+
if (!existingRl) rl.close();
|
|
860
1037
|
console.log(` ${faint('no changes made')}`);
|
|
861
1038
|
return;
|
|
862
1039
|
}
|
|
@@ -1007,7 +1184,7 @@ async function interactive() {
|
|
|
1007
1184
|
case 'benchmark': case 'bench':
|
|
1008
1185
|
clearScreen(); printCompactHeader('benchmark'); cmdBenchmark(); break;
|
|
1009
1186
|
case 'install':
|
|
1010
|
-
clearScreen(); printCompactHeader('install'); cmdInstall(rest); break;
|
|
1187
|
+
clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
|
|
1011
1188
|
case 'online':
|
|
1012
1189
|
clearScreen(); printCompactHeader('online'); cmdOnline(rest); break;
|
|
1013
1190
|
case 'settings': case 'config':
|
|
@@ -1033,6 +1210,25 @@ function printBye() {
|
|
|
1033
1210
|
console.log(`\n ${ok('✓')} ${bold(lblue('goodbye'))} ${faint('keep building')}\n`);
|
|
1034
1211
|
}
|
|
1035
1212
|
|
|
1213
|
+
// ── Update check ──────────────────────────────────────────────────────────────
|
|
1214
|
+
|
|
1215
|
+
async function checkForUpdate() {
|
|
1216
|
+
try {
|
|
1217
|
+
const result = spawnSync(
|
|
1218
|
+
'npm', ['view', 'context-mcp-server', 'version', '--json'],
|
|
1219
|
+
{ encoding: 'utf8', timeout: 3000, shell: true },
|
|
1220
|
+
);
|
|
1221
|
+
if (result.status !== 0 || !result.stdout) return;
|
|
1222
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
1223
|
+
const latest = typeof parsed === 'string' ? parsed : String(parsed);
|
|
1224
|
+
const current = pkg.version;
|
|
1225
|
+
if (latest && latest !== current) {
|
|
1226
|
+
console.log(` ${warn('↑')} ${bold('Update available')} ${faint(current)} ${accent('→')} ${ok(latest)} ${faint('run:')} ${accent('npm i -g context-mcp-server@latest')}`);
|
|
1227
|
+
console.log('');
|
|
1228
|
+
}
|
|
1229
|
+
} catch {}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1036
1232
|
// ── CLI entry point ───────────────────────────────────────────────────────────
|
|
1037
1233
|
|
|
1038
1234
|
(async () => {
|
|
@@ -1052,7 +1248,24 @@ function printBye() {
|
|
|
1052
1248
|
case 'benchmark': case 'bench':
|
|
1053
1249
|
cmdBenchmark(); break;
|
|
1054
1250
|
case 'install':
|
|
1055
|
-
cmdInstall(rest);
|
|
1251
|
+
await cmdInstall(rest);
|
|
1252
|
+
process.exit(0);
|
|
1253
|
+
break;
|
|
1254
|
+
case 'update': {
|
|
1255
|
+
printSection('Update');
|
|
1256
|
+
console.log('');
|
|
1257
|
+
const upd = spawnSync(
|
|
1258
|
+
'npm', ['install', '-g', 'context-mcp-server@latest'],
|
|
1259
|
+
{ encoding: 'utf8', shell: true, stdio: 'inherit' },
|
|
1260
|
+
);
|
|
1261
|
+
if (upd.status === 0) {
|
|
1262
|
+
console.log(`\n ${ok('✓')} ${bold('context-mcp updated to latest')}`);
|
|
1263
|
+
} else {
|
|
1264
|
+
console.log(`\n ${bad('✗')} update failed — try: ${accent('npm i -g context-mcp-server@latest')}`);
|
|
1265
|
+
}
|
|
1266
|
+
console.log('');
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1056
1269
|
case 'online':
|
|
1057
1270
|
cmdOnline(rest); break;
|
|
1058
1271
|
case 'settings': case 'config':
|
|
@@ -1062,10 +1275,13 @@ function printBye() {
|
|
|
1062
1275
|
case 'delete': case 'del': case 'rm':
|
|
1063
1276
|
cmdDelete(rest); break;
|
|
1064
1277
|
case 'help': case '--help': case '-h':
|
|
1065
|
-
|
|
1278
|
+
await checkForUpdate();
|
|
1279
|
+
printUsage();
|
|
1280
|
+
break;
|
|
1066
1281
|
case '--version': case '-v':
|
|
1067
1282
|
console.log(pkg.version); break;
|
|
1068
1283
|
default:
|
|
1284
|
+
await checkForUpdate();
|
|
1069
1285
|
await interactive();
|
|
1070
1286
|
}
|
|
1071
1287
|
})();
|
package/src/db.js
CHANGED
|
@@ -23,6 +23,11 @@ const PROJECTS_PATH = join(DATA_DIR, 'projects.json');
|
|
|
23
23
|
|
|
24
24
|
const MAX_CONTENT_LENGTH = 5000;
|
|
25
25
|
const PREVIEW_LENGTH = 200;
|
|
26
|
+
|
|
27
|
+
// Normalize file paths for cross-platform comparison (Windows case + slash variants)
|
|
28
|
+
function normPath(p) {
|
|
29
|
+
return p ? p.toLowerCase().replace(/\\/g, '/').replace(/\/$/, '') : '';
|
|
30
|
+
}
|
|
26
31
|
const WRITE_DEBOUNCE_MS = 500;
|
|
27
32
|
const LOCK_WAIT_TIMEOUT_MS = 2000;
|
|
28
33
|
|
|
@@ -126,9 +131,9 @@ function mergeStore(latest, local) {
|
|
|
126
131
|
if (_changedDiscussionNames.has(disc.name)) discussionsByName.set(disc.name, disc);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
const graphsByPath = new Map((latest.graphs || []).map(g => [g.path, g]));
|
|
134
|
+
const graphsByPath = new Map((latest.graphs || []).map(g => [normPath(g.path), g]));
|
|
130
135
|
for (const graph of (local.graphs || [])) {
|
|
131
|
-
if (_changedGraphPaths.has(graph.path)) graphsByPath.set(graph.path, graph);
|
|
136
|
+
if (_changedGraphPaths.has(graph.path)) graphsByPath.set(normPath(graph.path), graph);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
const projectsById = new Map((latest.projects || []).map(p => [p.id, p]));
|
|
@@ -713,7 +718,7 @@ export function flushStore() { flushToDisk(); }
|
|
|
713
718
|
|
|
714
719
|
// ── Auto-compaction ───────────────────────────────────────────────────────────
|
|
715
720
|
|
|
716
|
-
const COMPACTION_THRESHOLD =
|
|
721
|
+
const COMPACTION_THRESHOLD = 20;
|
|
717
722
|
const COMPACTION_TARGET = 30;
|
|
718
723
|
|
|
719
724
|
export function shouldCompact(project) {
|
|
@@ -753,7 +758,14 @@ export function compactProject(project, summaryContent) {
|
|
|
753
758
|
export function saveGraph({ path, nodes, edges, communities, cached, changed, time_ms, summary }) {
|
|
754
759
|
refreshFromDisk();
|
|
755
760
|
const store = load();
|
|
756
|
-
|
|
761
|
+
// Deduplicate: collapse any case/slash variants of same path, keep newest
|
|
762
|
+
const dupes = store.graphs.filter(g => normPath(g.path) === normPath(path));
|
|
763
|
+
if (dupes.length > 1) {
|
|
764
|
+
const keep = dupes.reduce((a, b) => (a.builtAt >= b.builtAt ? a : b));
|
|
765
|
+
store.graphs = store.graphs.filter(g => normPath(g.path) !== normPath(path));
|
|
766
|
+
store.graphs.push(keep);
|
|
767
|
+
}
|
|
768
|
+
const existing = store.graphs.find(g => normPath(g.path) === normPath(path));
|
|
757
769
|
const record = {
|
|
758
770
|
path,
|
|
759
771
|
nodes: nodes ?? existing?.nodes ?? 0,
|
|
@@ -777,7 +789,7 @@ export function saveGraph({ path, nodes, edges, communities, cached, changed, ti
|
|
|
777
789
|
|
|
778
790
|
export function getGraph(path) {
|
|
779
791
|
const store = load();
|
|
780
|
-
if (path) return store.graphs.find(g => g.path === path) || null;
|
|
792
|
+
if (path) return store.graphs.find(g => normPath(g.path) === normPath(path)) || null;
|
|
781
793
|
return store.graphs;
|
|
782
794
|
}
|
|
783
795
|
|
package/src/hooks/autoContext.js
CHANGED
|
@@ -3,7 +3,7 @@ import { fireAutoLink } from './autoLink.js';
|
|
|
3
3
|
|
|
4
4
|
export function saveAutoContext({ title, content, type, files, state, tags = [] }) {
|
|
5
5
|
const entry = saveContext({
|
|
6
|
-
project: state.sessionProject ||
|
|
6
|
+
project: state.sessionProject || null,
|
|
7
7
|
sessionId: state.sessionId || null,
|
|
8
8
|
title,
|
|
9
9
|
content,
|
package/src/templates/AGENTS.md
CHANGED
|
@@ -20,22 +20,40 @@ Then:
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## 2.
|
|
23
|
+
## 2. When to Auto-Save Context
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| Need past info | `search` before asking user |
|
|
25
|
+
**After graph build or rebuild** — every time `codegraph_build` completes:
|
|
26
|
+
```
|
|
27
|
+
context.save type: "architecture" title: "ContextGraph built — <project>"
|
|
28
|
+
content: "nodes: X | edges: Y | communities: Z"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**User explicitly asks** — "save this", "remember this", "note that" → save immediately.
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
**During plan / implementation / discussion / research** — save only when genuinely valuable:
|
|
34
|
+
|
|
35
|
+
| What happened | Type |
|
|
36
|
+
|--------------|------|
|
|
37
|
+
| Approach / library / pattern decided | `decision` |
|
|
38
|
+
| Bug found (root cause known) or fixed | `bug` |
|
|
39
|
+
| System structure understood | `architecture` |
|
|
40
|
+
| Gotcha, constraint, non-obvious behavior | `note` |
|
|
41
|
+
| Config / env var / secret key discovered | `config` |
|
|
42
|
+
| External API or service integration learned | `note` |
|
|
43
|
+
| Performance insight (why something is slow/fast) | `note` |
|
|
44
|
+
| How to run tests / test pattern discovered | `note` |
|
|
45
|
+
| Deploy / release step discovered | `note` |
|
|
46
|
+
| Milestone / feature / task completed | `note` |
|
|
47
|
+
|
|
48
|
+
Do NOT save: routine reads, search results, temporary debugging dead-ends.
|
|
49
|
+
Feature spans sessions → `discussion.save` status: `"active"`.
|
|
50
|
+
Need past info → `search` before asking. Always pass `project`.
|
|
35
51
|
|
|
36
52
|
---
|
|
37
53
|
|
|
38
|
-
## 3.
|
|
54
|
+
## 3. ContextGraph Pipeline
|
|
55
|
+
|
|
56
|
+
> The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
|
|
39
57
|
|
|
40
58
|
### Step 1 — Build (once, fast, local)
|
|
41
59
|
```
|
package/src/templates/CLAUDE.md
CHANGED
|
@@ -15,7 +15,12 @@ Every conversation starts with `context.resume`. Every codebase question uses `c
|
|
|
15
15
|
|
|
16
16
|
## 1. Start of Every Conversation (MANDATORY)
|
|
17
17
|
|
|
18
|
-
Call `context` tool
|
|
18
|
+
Call `context` tool **before anything else** with:
|
|
19
|
+
- `action: "resume"`
|
|
20
|
+
- `project: "<basename of git repo root dir>"` — infer from cwd if not stated
|
|
21
|
+
- `rootPath: "<absolute path to git repo root>"` — required for sandbox + graph lookup
|
|
22
|
+
|
|
23
|
+
Both fields are required: `project` names the memory bucket, `rootPath` enables exact graph matching and file sandboxing.
|
|
19
24
|
|
|
20
25
|
Returns:
|
|
21
26
|
- `recentEntries` — decisions, bugs, notes from previous conversations
|
|
@@ -28,22 +33,44 @@ Then:
|
|
|
28
33
|
|
|
29
34
|
---
|
|
30
35
|
|
|
31
|
-
## 2.
|
|
36
|
+
## 2. When to Auto-Save Context
|
|
37
|
+
|
|
38
|
+
### Always save — no user prompt needed
|
|
39
|
+
|
|
40
|
+
**After graph build or rebuild** — every time `codegraph_build` completes:
|
|
41
|
+
```
|
|
42
|
+
context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
|
|
43
|
+
content: "nodes: X | edges: Y | communities: Z"
|
|
44
|
+
```
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|-----------|--------|
|
|
35
|
-
| Decision made | `context.save` type: `"decision"` |
|
|
36
|
-
| Bug found/fixed | `context.save` type: `"bug"` |
|
|
37
|
-
| Architecture understood | `context.save` type: `"architecture"` |
|
|
38
|
-
| User says "save/remember this" | `context.save` immediately |
|
|
39
|
-
| Feature spans multiple conversations | `discussion.create` or `discussion.update` |
|
|
40
|
-
| Need past info | `search` before asking user |
|
|
46
|
+
**User explicitly asks** — any phrase like "save this", "remember this", "note that" → save immediately.
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
**During plan / implementation / discussion / research** — save only when genuinely valuable:
|
|
49
|
+
|
|
50
|
+
| What happened | Type |
|
|
51
|
+
|--------------|------|
|
|
52
|
+
| Approach / library / pattern decided | `decision` |
|
|
53
|
+
| Bug found (root cause known) or fixed | `bug` |
|
|
54
|
+
| System structure understood | `architecture` |
|
|
55
|
+
| Gotcha, constraint, non-obvious behavior | `note` |
|
|
56
|
+
| Config / env var / secret key discovered | `config` |
|
|
57
|
+
| External API or service integration learned | `note` |
|
|
58
|
+
| Performance insight (why something is slow/fast) | `note` |
|
|
59
|
+
| How to run tests / test pattern discovered | `note` |
|
|
60
|
+
| Deploy / release step discovered | `note` |
|
|
61
|
+
| Milestone / feature / task completed | `note` |
|
|
62
|
+
|
|
63
|
+
Do NOT save: routine reads, search results, temporary debugging dead-ends.
|
|
64
|
+
|
|
65
|
+
Feature spans multiple sessions → `discussion.create` or `discussion.update`.
|
|
66
|
+
Need past info → `search` before asking user.
|
|
67
|
+
Always pass `project`. Auto-compact fires at >20 entries.
|
|
43
68
|
|
|
44
69
|
---
|
|
45
70
|
|
|
46
|
-
## 3.
|
|
71
|
+
## 3. ContextGraph Pipeline
|
|
72
|
+
|
|
73
|
+
> The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
|
|
47
74
|
|
|
48
75
|
### Step 1 — Build (once per project, fast, local)
|
|
49
76
|
```
|
package/src/templates/GEMINI.md
CHANGED
|
@@ -20,22 +20,40 @@ Then:
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## 2.
|
|
23
|
+
## 2. When to Auto-Save Context
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| Need past info | `search` before asking user |
|
|
25
|
+
**After graph build or rebuild** — every time `codegraph_build` completes:
|
|
26
|
+
```
|
|
27
|
+
context.save type: "architecture" title: "ContextGraph built — <project>"
|
|
28
|
+
content: "nodes: X | edges: Y | communities: Z"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**User explicitly asks** — "save this", "remember this", "note that" → save immediately.
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
**During plan / implementation / discussion / research** — save only when genuinely valuable:
|
|
34
|
+
|
|
35
|
+
| What happened | Type |
|
|
36
|
+
|--------------|------|
|
|
37
|
+
| Approach / library / pattern decided | `decision` |
|
|
38
|
+
| Bug found (root cause known) or fixed | `bug` |
|
|
39
|
+
| System structure understood | `architecture` |
|
|
40
|
+
| Gotcha, constraint, non-obvious behavior | `note` |
|
|
41
|
+
| Config / env var / secret key discovered | `config` |
|
|
42
|
+
| External API or service integration learned | `note` |
|
|
43
|
+
| Performance insight (why something is slow/fast) | `note` |
|
|
44
|
+
| How to run tests / test pattern discovered | `note` |
|
|
45
|
+
| Deploy / release step discovered | `note` |
|
|
46
|
+
| Milestone / feature / task completed | `note` |
|
|
47
|
+
|
|
48
|
+
Do NOT save: routine reads, search results, temporary debugging dead-ends.
|
|
49
|
+
Feature spans sessions → `discussion.save` then `discussion.update`.
|
|
50
|
+
Need past info → `search` before asking. Always pass `project`.
|
|
35
51
|
|
|
36
52
|
---
|
|
37
53
|
|
|
38
|
-
## 3.
|
|
54
|
+
## 3. ContextGraph Pipeline
|
|
55
|
+
|
|
56
|
+
> The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
|
|
39
57
|
|
|
40
58
|
### Step 1 — Build (once, fast, local)
|
|
41
59
|
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Call the `context` MCP tool with `action: "resume"`, `project: "$ARGUMENTS"` (if no argument given, infer the project name from the current working directory name), and `rootPath: "<absolute path to the project root / git repo root>"`.
|
|
2
|
+
|
|
3
|
+
Both `project` and `rootPath` are required: `project` names the memory bucket, `rootPath` enables exact graph lookup and file sandboxing.
|
|
4
|
+
|
|
5
|
+
This loads:
|
|
6
|
+
- Recent decisions, bugs, and notes from past sessions
|
|
7
|
+
- Active discussions
|
|
8
|
+
- ContextGraph status (built or not)
|
|
9
|
+
|
|
10
|
+
If `codegraph.built` is false in the response, immediately call `codegraph_build` on the project path before proceeding.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Call `codegraph_build` with the path `$ARGUMENTS` (if no argument given, use the current working directory).
|
|
2
|
+
|
|
3
|
+
This builds the ContextGraph for the project — parses all source files into an AST knowledge graph using tree-sitter. Takes a few seconds. Once built, use `codegraph_query` to answer any structural question about the codebase instead of reading files directly.
|
|
4
|
+
|
|
5
|
+
After build completes, report: total nodes, edges, and communities found.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Call the `context` MCP tool with `action: "save"` to store a note for the current project.
|
|
2
|
+
|
|
3
|
+
Parse `$ARGUMENTS` to determine:
|
|
4
|
+
- `project`: infer from current working directory name if not specified
|
|
5
|
+
- `title`: first sentence or phrase from the argument
|
|
6
|
+
- `content`: full argument text
|
|
7
|
+
- `type`: auto-detect — if mentions a bug/fix → `"bug"`, decision/chose/decided → `"decision"`, structure/architecture → `"architecture"`, otherwise `"note"`
|
|
8
|
+
|
|
9
|
+
Confirm to the user what was saved (title, type, project).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context-mcp
|
|
3
|
+
description: >
|
|
4
|
+
Persistent memory + ContextGraph (codebase knowledge graph) for Claude.
|
|
5
|
+
Use at the START of every conversation to resume project context. Use
|
|
6
|
+
whenever the user mentions a project, asks to remember/save something,
|
|
7
|
+
references past work, or says "pick up where we left off". Also use
|
|
8
|
+
when the user asks about code structure, dependencies, or what exists
|
|
9
|
+
in a codebase — query the ContextGraph before reading any files.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Context-MCP
|
|
13
|
+
|
|
14
|
+
Persistent memory + codebase knowledge graph across every conversation.
|
|
15
|
+
`context.resume` starts every session. `codegraph_query` answers every structure question. Files only for bugs/logic.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## MANDATORY: Start of Every Conversation
|
|
20
|
+
|
|
21
|
+
Call `context` tool **before any tool or response** with:
|
|
22
|
+
- `action: "resume"`
|
|
23
|
+
- `project: "<basename of git repo root dir>"` — infer from `cwd` if not stated
|
|
24
|
+
- `rootPath: "<absolute path to git repo root>"` — required for sandbox + graph lookup
|
|
25
|
+
|
|
26
|
+
Both fields are required: `project` names the memory bucket, `rootPath` enables exact graph matching and file sandboxing.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
- `recentEntries` — decisions, bugs, notes from past sessions
|
|
30
|
+
- `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
|
|
31
|
+
- `codegraph` — `{ built: true/false, nodes, edges, communities }`
|
|
32
|
+
|
|
33
|
+
Then:
|
|
34
|
+
- `codegraph.built: true` → use `codegraph_query` before reading any files
|
|
35
|
+
- `codegraph.built: false` → call `codegraph_build(path)` first, then proceed
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## When to Auto-Save Context
|
|
40
|
+
|
|
41
|
+
### Always save — no user prompt needed
|
|
42
|
+
|
|
43
|
+
**1. After graph build or rebuild**
|
|
44
|
+
Every time `codegraph_build` completes successfully, immediately call:
|
|
45
|
+
```
|
|
46
|
+
context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
|
|
47
|
+
content: "nodes: X | edges: Y | communities: Z | built: <timestamp>"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**2. When user explicitly asks**
|
|
51
|
+
Any phrase like "save this", "remember this", "note that", "log this" → `context.save` immediately with whatever was just discussed.
|
|
52
|
+
|
|
53
|
+
**3. During plan / implementation / discussion / research — save when something valuable happens**
|
|
54
|
+
Only save if the moment is genuinely worth keeping across sessions:
|
|
55
|
+
- A decision was made and agreed on (approach, library, pattern, architecture)
|
|
56
|
+
- A bug was found with its root cause identified, or fixed
|
|
57
|
+
- An important discovery (gotcha, constraint, non-obvious behavior, env requirement)
|
|
58
|
+
- A significant milestone reached (feature complete, refactor done, plan finalized)
|
|
59
|
+
- Something that would save future-you from re-learning it
|
|
60
|
+
|
|
61
|
+
Do NOT save for: routine file reads, search results, explanations of existing code, temporary debugging steps that led nowhere.
|
|
62
|
+
|
|
63
|
+
| What happened | Type |
|
|
64
|
+
|--------------|------|
|
|
65
|
+
| Approach / library / pattern decided | `decision` |
|
|
66
|
+
| Bug found (root cause known) or fixed | `bug` |
|
|
67
|
+
| System structure understood | `architecture` |
|
|
68
|
+
| Gotcha, constraint, non-obvious behavior | `note` |
|
|
69
|
+
| Config / env var / secret key discovered | `config` |
|
|
70
|
+
| External API or service integration learned | `note` |
|
|
71
|
+
| Performance insight (why something is slow/fast) | `note` |
|
|
72
|
+
| How to run tests / test pattern discovered | `note` |
|
|
73
|
+
| Deploy / release step discovered | `note` |
|
|
74
|
+
| Milestone / feature / task completed | `note` |
|
|
75
|
+
|
|
76
|
+
Always pass `project`. Feature spans multiple sessions → `discussion.create` or `discussion.update`. Need past info → `search` before asking user. Auto-compact fires at >20 entries.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ContextGraph Pipeline
|
|
81
|
+
|
|
82
|
+
> Also called **CodeGraph**. MCP tools use the `codegraph_*` prefix — both names mean the same thing.
|
|
83
|
+
|
|
84
|
+
### Build (once per project, ~seconds, local)
|
|
85
|
+
```
|
|
86
|
+
codegraph_build(path)
|
|
87
|
+
```
|
|
88
|
+
Parses codebase into AST graph via tree-sitter. Extracts functions, classes, imports, call edges for 16+ languages. Build files get a single metadata node. Saved to `~/.context-mcp/graphs.json`.
|
|
89
|
+
|
|
90
|
+
### Query (free, instant, forever)
|
|
91
|
+
```
|
|
92
|
+
codegraph_query(path, question) → find functions, classes, files, dependencies, callers
|
|
93
|
+
codegraph_explain(path, node) → one node: type, file, depends_on, used_by
|
|
94
|
+
codegraph_path(path, from, to) → shortest path between two concepts
|
|
95
|
+
codegraph_nodes(path, type) → list all nodes of a type
|
|
96
|
+
codegraph_report(path) → god nodes, clusters, surprises
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Graph vs File
|
|
102
|
+
|
|
103
|
+
**Graph** — what exists: finding functions, classes, files, dependencies, callers, imports, paths between concepts.
|
|
104
|
+
|
|
105
|
+
**File** — bugs, logic inside a specific function, tracing unexpected behavior.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Rules
|
|
110
|
+
|
|
111
|
+
1. **`context.resume` first** — before any tool or response
|
|
112
|
+
2. **Always pass `project`** — never save to global unless truly cross-project
|
|
113
|
+
3. **`search` before asking** — if user references past work, find it first
|
|
114
|
+
4. **`codegraph_query` before reading files** — graph is faster and cheaper
|
|
115
|
+
5. **Read files for bugs/logic only** — graph is structure, not behavior
|
package/src/tools/codegraph.js
CHANGED
|
@@ -184,7 +184,10 @@ export function handle(name, args, state) {
|
|
|
184
184
|
summary: result.summary || '',
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
-
const
|
|
187
|
+
const inferredProject = args.path
|
|
188
|
+
? args.path.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop()
|
|
189
|
+
: null;
|
|
190
|
+
const project = state?.sessionProject || inferredProject || null;
|
|
188
191
|
const title = `CodeGraph — ${args.path}`;
|
|
189
192
|
const content = [
|
|
190
193
|
`nodes: ${result.nodes} | edges: ${result.edges} | communities: ${result.communities}`,
|
package/src/tools/context.js
CHANGED
|
@@ -92,9 +92,15 @@ export async function handle(args, state) {
|
|
|
92
92
|
.filter(e => e.status !== 'archived');
|
|
93
93
|
const discussions = listDiscussions({ project: proj, status: 'active' });
|
|
94
94
|
const allGraphs = listGraphs();
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
const np = p => (p || '').toLowerCase().replace(/\\/g, '/');
|
|
96
|
+
const graph = resolvedRoot
|
|
97
|
+
? allGraphs.find(g => np(g.path) === np(resolvedRoot)) || null
|
|
98
|
+
: proj
|
|
99
|
+
? allGraphs.find(g => {
|
|
100
|
+
const parts = np(g.path).split('/');
|
|
101
|
+
return parts[parts.length - 1] === proj.toLowerCase();
|
|
102
|
+
}) || null
|
|
103
|
+
: null;
|
|
98
104
|
const totalEntries = countContext(proj);
|
|
99
105
|
|
|
100
106
|
// Auto-restore single active discussion
|
package/src/tools/gitTools.js
CHANGED
|
@@ -29,11 +29,10 @@ function resolveCwd(args, state) {
|
|
|
29
29
|
// Auto-detect project root on first use if not already configured.
|
|
30
30
|
if (!state.projectRootPath) {
|
|
31
31
|
const detected = autoDetectRoot(args.cwd ? pathResolve(args.cwd) : process.cwd());
|
|
32
|
-
|
|
32
|
+
state.projectRootPath = detected || process.cwd();
|
|
33
33
|
}
|
|
34
|
-
const raw = args.cwd ? pathResolve(args.cwd) :
|
|
35
|
-
|
|
36
|
-
return raw;
|
|
34
|
+
const raw = args.cwd ? pathResolve(args.cwd) : state.projectRootPath;
|
|
35
|
+
return guardPath(raw, state.projectRootPath);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
const ROOT_NOTE = ' All paths must be within the project root (sandboxed — access outside root is denied).';
|