context-mcp-server 1.0.3 → 1.0.5
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 +3 -1
- package/pyproject.toml +69 -0
- package/src/cli.js +249 -60
- package/src/templates/AGENTS.md +29 -11
- package/src/templates/CLAUDE.md +33 -11
- package/src/templates/GEMINI.md +29 -11
- package/src/templates/commands/context-resume.md +8 -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 +112 -0
- package/uv.lock +1295 -0
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.5",
|
|
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": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"src/",
|
|
23
23
|
"codegraph/",
|
|
24
|
+
"pyproject.toml",
|
|
25
|
+
"uv.lock",
|
|
24
26
|
"README.md"
|
|
25
27
|
],
|
|
26
28
|
"keywords": [
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codegraph-mcp"
|
|
7
|
+
version = "1.0.5"
|
|
8
|
+
description = "Codebase knowledge graph MCP server — AST extraction, graph queries, community detection"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["mcp", "ai", "codegraph", "knowledge-graph", "ast"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"mcp>=1.0.0",
|
|
15
|
+
"networkx>=3.0",
|
|
16
|
+
"pymupdf>=1.24",
|
|
17
|
+
"tree-sitter>=0.23.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
treesitter = [
|
|
22
|
+
"tree-sitter>=0.23.0",
|
|
23
|
+
"tree-sitter-python",
|
|
24
|
+
"tree-sitter-javascript",
|
|
25
|
+
"tree-sitter-typescript",
|
|
26
|
+
"tree-sitter-go",
|
|
27
|
+
"tree-sitter-rust",
|
|
28
|
+
"tree-sitter-java",
|
|
29
|
+
"tree-sitter-c",
|
|
30
|
+
"tree-sitter-cpp",
|
|
31
|
+
"tree-sitter-c-sharp",
|
|
32
|
+
"tree-sitter-ruby",
|
|
33
|
+
"tree-sitter-php",
|
|
34
|
+
"tree-sitter-swift",
|
|
35
|
+
"tree-sitter-lua",
|
|
36
|
+
"tree-sitter-kotlin",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
codegraph-mcp = "codegraph.server:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["codegraph"]
|
|
44
|
+
|
|
45
|
+
# ── uv ────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"pytest>=8.0",
|
|
50
|
+
"pytest-asyncio>=0.23",
|
|
51
|
+
"ruff>=0.4",
|
|
52
|
+
"mypy>=1.10",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# ── ruff ─────────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
[tool.ruff]
|
|
58
|
+
target-version = "py311"
|
|
59
|
+
line-length = 100
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint]
|
|
62
|
+
select = ["E", "F", "I"]
|
|
63
|
+
ignore = ["E501"]
|
|
64
|
+
|
|
65
|
+
# ── pytest ────────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
[tool.pytest.ini_options]
|
|
68
|
+
asyncio_mode = "auto"
|
|
69
|
+
testpaths = ["tests"]
|
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
|
|
@@ -531,65 +549,163 @@ 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
|
+
function _updateGlobalGitignore() {
|
|
571
|
+
// Resolve global gitignore path: git config > ~/.gitignore_global > ~/.gitignore
|
|
572
|
+
let giPath = null;
|
|
573
|
+
const gitCfg = spawnSync('git', ['config', '--global', 'core.excludesFile'], { encoding: 'utf8' });
|
|
574
|
+
if (gitCfg.status === 0 && gitCfg.stdout.trim()) {
|
|
575
|
+
const resolved = gitCfg.stdout.trim().replace(/^~/, homedir());
|
|
576
|
+
if (existsSync(resolved)) giPath = resolved;
|
|
577
|
+
}
|
|
578
|
+
if (!giPath) {
|
|
579
|
+
for (const candidate of [join(homedir(), '.gitignore_global'), join(homedir(), '.gitignore')]) {
|
|
580
|
+
if (existsSync(candidate)) { giPath = candidate; break; }
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (!giPath) return; // no global gitignore — skip silently
|
|
584
|
+
|
|
585
|
+
const existing = readFileSync(giPath, 'utf8');
|
|
586
|
+
const lines = existing.split(/\r?\n/);
|
|
587
|
+
const missing = _GLOBAL_GITIGNORE_ENTRIES.filter(e => !lines.includes(e));
|
|
588
|
+
if (!missing.length) return;
|
|
589
|
+
|
|
590
|
+
const block = '\n# context-mcp — written by ctx install\n' + missing.join('\n') + '\n';
|
|
591
|
+
writeFileSync(giPath, existing.trimEnd() + block, 'utf8');
|
|
592
|
+
console.log(` ${ok('✓')} ${'global gitignore'.padEnd(28)} ${faint(giPath.replace(/\\/g, '/'))}`);
|
|
593
|
+
for (const e of missing) console.log(` ${faint('+ ' + e)}`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function _writeCommands(baseDir) {
|
|
597
|
+
const cmdsDir = join(TPLS, 'commands');
|
|
598
|
+
const destDir = join(baseDir, '.claude', 'commands');
|
|
599
|
+
for (const [name, label] of [
|
|
600
|
+
['context-resume.md', '/context-resume'],
|
|
601
|
+
['graph-build.md', '/graph-build'],
|
|
602
|
+
['save-context.md', '/save-context'],
|
|
603
|
+
]) {
|
|
604
|
+
const src = join(cmdsDir, name);
|
|
605
|
+
if (existsSync(src)) {
|
|
606
|
+
_writeFile(join(destDir, name), readFileSync(src, 'utf8'), label);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const MCP_SERVER_CMD = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
|
|
612
|
+
|
|
534
613
|
const PLATFORMS = {
|
|
535
614
|
claude: {
|
|
536
615
|
label: 'Claude Code',
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
616
|
+
restartNote: 'Type /context-resume in Claude Code to start using context-mcp.',
|
|
617
|
+
install(dir, scope) {
|
|
618
|
+
// Install as a skill (global, works across all projects) instead of a flat CLAUDE.md
|
|
619
|
+
const skillSrc = join(TPLS, 'skills', 'SKILL.md');
|
|
620
|
+
const skillDest = join(homedir(), '.claude', 'skills', 'context-mcp', 'SKILL.md');
|
|
621
|
+
if (existsSync(skillSrc)) {
|
|
622
|
+
_writeFile(skillDest, readFileSync(skillSrc, 'utf8'), '~/.claude/skills/context-mcp/');
|
|
623
|
+
}
|
|
624
|
+
// Slash commands are always user-global (not project-scoped)
|
|
625
|
+
_writeCommands(homedir());
|
|
626
|
+
// Register via claude CLI so the server is trusted immediately (no manual trust prompt)
|
|
627
|
+
const scopeFlag = scope === 'global' ? 'user' : 'project';
|
|
628
|
+
const reg = spawnSync(
|
|
629
|
+
'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
630
|
+
{ encoding: 'utf8', shell: true },
|
|
631
|
+
);
|
|
632
|
+
if (reg.status === 0) {
|
|
633
|
+
console.log(` ${ok('✓')} ${'registered via claude mcp add'.padEnd(28)} ${faint('scope: ' + scopeFlag)}`);
|
|
634
|
+
} else {
|
|
635
|
+
console.log(` ${faint('ℹ')} claude CLI not found — open Claude Code and trust context-mcp when prompted`);
|
|
636
|
+
}
|
|
544
637
|
},
|
|
545
638
|
},
|
|
546
639
|
cursor: {
|
|
547
640
|
label: 'Cursor',
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
641
|
+
restartNote: 'Restart Cursor to load the new MCP server.',
|
|
642
|
+
install(dir, scope) {
|
|
643
|
+
const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
644
|
+
_writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
|
|
645
|
+
if (scope === 'project') {
|
|
646
|
+
const mdc = _tpl('cursor-rules.mdc');
|
|
647
|
+
if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
|
|
648
|
+
}
|
|
649
|
+
// Try to enable via cursor CLI (enable/disable only — no add command)
|
|
650
|
+
const reg = spawnSync(
|
|
651
|
+
'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
|
|
652
|
+
{ encoding: 'utf8', shell: true },
|
|
653
|
+
);
|
|
654
|
+
if (reg.status === 0) {
|
|
655
|
+
console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
|
|
656
|
+
}
|
|
555
657
|
},
|
|
556
658
|
},
|
|
557
659
|
vscode: {
|
|
558
660
|
label: 'VS Code Copilot',
|
|
559
|
-
|
|
661
|
+
restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
|
|
662
|
+
install(dir) {
|
|
560
663
|
const mcpJson = JSON.stringify({
|
|
561
|
-
servers: { 'context-mcp': { type: 'stdio',
|
|
664
|
+
servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
|
|
562
665
|
}, null, 2);
|
|
563
|
-
_writeFile(join(
|
|
564
|
-
const md = _tpl('CLAUDE.md');
|
|
565
|
-
if (md) _writeFile(join(cwd, 'CLAUDE.md'), md, 'CLAUDE.md');
|
|
666
|
+
_writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
|
|
566
667
|
},
|
|
567
668
|
},
|
|
568
669
|
gemini: {
|
|
569
670
|
label: 'Gemini CLI',
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
671
|
+
restartNote: 'Restart your Gemini CLI session.',
|
|
672
|
+
install(dir, scope) {
|
|
673
|
+
const cfg = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
674
|
+
_writeFile(join(dir, '.gemini', 'settings.json'), cfg, '.gemini/settings.json');
|
|
675
|
+
if (scope === 'project') {
|
|
676
|
+
const md = _tpl('GEMINI.md');
|
|
677
|
+
if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
|
|
678
|
+
}
|
|
577
679
|
},
|
|
578
680
|
},
|
|
579
681
|
codex: {
|
|
580
682
|
label: 'Codex CLI',
|
|
581
|
-
|
|
683
|
+
restartNote: 'Restart your Codex CLI session.',
|
|
684
|
+
install(dir, scope) {
|
|
582
685
|
const toml = `[[mcp_servers]]\nname = "context-mcp"\ncommand = "npx"\nargs = ["-y", "context-mcp-server@latest"]\n`;
|
|
583
|
-
_writeFile(join(
|
|
584
|
-
|
|
585
|
-
|
|
686
|
+
_writeFile(join(dir, '.codex', 'config.toml'), toml, '.codex/config.toml');
|
|
687
|
+
if (scope === 'project') {
|
|
688
|
+
const md = _tpl('AGENTS.md');
|
|
689
|
+
if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
|
|
690
|
+
}
|
|
691
|
+
// Register via codex CLI so server is active immediately
|
|
692
|
+
const reg = spawnSync(
|
|
693
|
+
'codex', ['mcp', 'add', 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
694
|
+
{ encoding: 'utf8', shell: true },
|
|
695
|
+
);
|
|
696
|
+
if (reg.status === 0) {
|
|
697
|
+
console.log(` ${ok('✓')} ${'registered via codex mcp add'.padEnd(28)}`);
|
|
698
|
+
} else {
|
|
699
|
+
console.log(` ${faint('ℹ')} codex CLI not found — server will load on next Codex session`);
|
|
700
|
+
}
|
|
586
701
|
},
|
|
587
702
|
},
|
|
588
703
|
windsurf: {
|
|
589
704
|
label: 'Windsurf',
|
|
590
|
-
|
|
705
|
+
restartNote: 'Restart Windsurf to load the updated MCP config.',
|
|
706
|
+
install(dir) {
|
|
591
707
|
const rules = _tpl('windsurf-rules.md');
|
|
592
|
-
if (rules) _writeFile(join(
|
|
708
|
+
if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
|
|
593
709
|
const globalCfgPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
594
710
|
let existing = {};
|
|
595
711
|
try { existing = JSON.parse(readFileSync(globalCfgPath, 'utf8')); } catch {}
|
|
@@ -600,7 +716,7 @@ const PLATFORMS = {
|
|
|
600
716
|
},
|
|
601
717
|
};
|
|
602
718
|
|
|
603
|
-
function cmdInstall(args) {
|
|
719
|
+
async function cmdInstall(args) {
|
|
604
720
|
const flags = new Set(args.map(a => a.replace(/^--/, '')));
|
|
605
721
|
const all = flags.has('all');
|
|
606
722
|
const initial = flags.has('initial');
|
|
@@ -637,7 +753,7 @@ function cmdInstall(args) {
|
|
|
637
753
|
const venvPath = join(pkgRootInit, '.venv');
|
|
638
754
|
spawnSync('cmd', ['/c', 'rmdir', '/s', '/q', venvPath], { encoding: 'utf8' });
|
|
639
755
|
}
|
|
640
|
-
const sync2 = spawnSync('uv', ['sync', '--no-dev'], {
|
|
756
|
+
const sync2 = spawnSync('uv', ['--directory', pkgRootInit, 'sync', '--no-dev'], { encoding: 'utf8' });
|
|
641
757
|
if (sync2.status !== 0) {
|
|
642
758
|
console.log(` ${bad('✗')} uv sync failed:\n${faint((sync2.stderr || sync2.stdout || '').trim())}`);
|
|
643
759
|
} else {
|
|
@@ -665,22 +781,56 @@ function cmdInstall(args) {
|
|
|
665
781
|
return;
|
|
666
782
|
}
|
|
667
783
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
784
|
+
// ── Scope prompt (skip for windsurf-only installs — it's always global) ──────
|
|
785
|
+
const nonWindsurf = keys.filter(k => k !== 'windsurf');
|
|
786
|
+
let scope = 'project';
|
|
787
|
+
let baseDir = process.cwd();
|
|
788
|
+
|
|
789
|
+
if (nonWindsurf.length > 0) {
|
|
790
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
791
|
+
const ask = q => new Promise(resolve => rl.question(` ${accent('›')} ${muted(q)} `, resolve));
|
|
792
|
+
|
|
793
|
+
printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
|
|
794
|
+
console.log('');
|
|
795
|
+
console.log(` ${muted('Install scope:')}`);
|
|
796
|
+
console.log(` ${accent('1.')} For this project ${faint('(writes config into current directory)')}`);
|
|
797
|
+
console.log(` ${accent('2.')} Globally ${faint('(writes config to your home directory)')}`);
|
|
798
|
+
console.log('');
|
|
799
|
+
const answer = (await ask('Choose (1/2) [1]:')).trim();
|
|
800
|
+
rl.close();
|
|
801
|
+
console.log('');
|
|
802
|
+
|
|
803
|
+
if (answer === '2') {
|
|
804
|
+
scope = 'global';
|
|
805
|
+
baseDir = homedir();
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
|
|
809
|
+
console.log('');
|
|
810
|
+
}
|
|
671
811
|
|
|
672
812
|
for (const key of keys) {
|
|
673
|
-
|
|
813
|
+
const platform = PLATFORMS[key];
|
|
814
|
+
console.log(` ${bold(lblue(platform.label))}`);
|
|
815
|
+
const dir = key === 'windsurf' ? process.cwd() : baseDir;
|
|
674
816
|
try {
|
|
675
|
-
|
|
817
|
+
platform.install(dir, scope);
|
|
676
818
|
} catch (err) {
|
|
677
819
|
console.log(` ${bad('✗')} failed: ${err.message}`);
|
|
678
820
|
}
|
|
821
|
+
if (platform.restartNote) {
|
|
822
|
+
console.log(` ${faint('→ ' + platform.restartNote)}`);
|
|
823
|
+
}
|
|
679
824
|
console.log('');
|
|
680
825
|
}
|
|
681
826
|
|
|
827
|
+
const destLabel = scope === 'global' ? homedir().replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/');
|
|
682
828
|
console.log(line());
|
|
683
|
-
console.log(faint(` ${keys.length} platform(s) installed
|
|
829
|
+
console.log(faint(` ${keys.length} platform(s) installed · scope: ${scope} · ${destLabel}`));
|
|
830
|
+
console.log('');
|
|
831
|
+
|
|
832
|
+
// ── Global gitignore — add context-mcp runtime files if global gitignore exists ──
|
|
833
|
+
_updateGlobalGitignore();
|
|
684
834
|
console.log('');
|
|
685
835
|
|
|
686
836
|
// ── Python / uv setup (codegraph) ─────────────────────────────────────────
|
|
@@ -696,7 +846,7 @@ function cmdInstall(args) {
|
|
|
696
846
|
// Package root is one level up from src/
|
|
697
847
|
const __dirname_cli = dirname(fileURLToPath(import.meta.url));
|
|
698
848
|
const pkgRoot = join(__dirname_cli, '..');
|
|
699
|
-
const sync = spawnSync('uv', ['sync', '--no-dev'], {
|
|
849
|
+
const sync = spawnSync('uv', ['--directory', pkgRoot, 'sync', '--no-dev'], { encoding: 'utf8' });
|
|
700
850
|
if (sync.status !== 0) {
|
|
701
851
|
console.log(` ${bad('✗')} uv sync failed:\n${faint((sync.stderr || sync.stdout || '').trim())}`);
|
|
702
852
|
} else {
|
|
@@ -853,10 +1003,10 @@ async function cmdSettings(existingRl) {
|
|
|
853
1003
|
console.log('');
|
|
854
1004
|
|
|
855
1005
|
const choice = (await ask('Edit field (1-' + FIELDS.length + '):')).trim();
|
|
856
|
-
if (!existingRl) rl.close();
|
|
857
1006
|
|
|
858
1007
|
const idx = parseInt(choice) - 1;
|
|
859
1008
|
if (isNaN(idx) || idx < 0 || idx >= FIELDS.length) {
|
|
1009
|
+
if (!existingRl) rl.close();
|
|
860
1010
|
console.log(` ${faint('no changes made')}`);
|
|
861
1011
|
return;
|
|
862
1012
|
}
|
|
@@ -1007,7 +1157,7 @@ async function interactive() {
|
|
|
1007
1157
|
case 'benchmark': case 'bench':
|
|
1008
1158
|
clearScreen(); printCompactHeader('benchmark'); cmdBenchmark(); break;
|
|
1009
1159
|
case 'install':
|
|
1010
|
-
clearScreen(); printCompactHeader('install'); cmdInstall(rest); break;
|
|
1160
|
+
clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
|
|
1011
1161
|
case 'online':
|
|
1012
1162
|
clearScreen(); printCompactHeader('online'); cmdOnline(rest); break;
|
|
1013
1163
|
case 'settings': case 'config':
|
|
@@ -1033,6 +1183,25 @@ function printBye() {
|
|
|
1033
1183
|
console.log(`\n ${ok('✓')} ${bold(lblue('goodbye'))} ${faint('keep building')}\n`);
|
|
1034
1184
|
}
|
|
1035
1185
|
|
|
1186
|
+
// ── Update check ──────────────────────────────────────────────────────────────
|
|
1187
|
+
|
|
1188
|
+
async function checkForUpdate() {
|
|
1189
|
+
try {
|
|
1190
|
+
const result = spawnSync(
|
|
1191
|
+
'npm', ['view', 'context-mcp-server', 'version', '--json'],
|
|
1192
|
+
{ encoding: 'utf8', timeout: 3000, shell: true },
|
|
1193
|
+
);
|
|
1194
|
+
if (result.status !== 0 || !result.stdout) return;
|
|
1195
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
1196
|
+
const latest = typeof parsed === 'string' ? parsed : String(parsed);
|
|
1197
|
+
const current = pkg.version;
|
|
1198
|
+
if (latest && latest !== current) {
|
|
1199
|
+
console.log(` ${warn('↑')} ${bold('Update available')} ${faint(current)} ${accent('→')} ${ok(latest)} ${faint('run:')} ${accent('npm i -g context-mcp-server@latest')}`);
|
|
1200
|
+
console.log('');
|
|
1201
|
+
}
|
|
1202
|
+
} catch {}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1036
1205
|
// ── CLI entry point ───────────────────────────────────────────────────────────
|
|
1037
1206
|
|
|
1038
1207
|
(async () => {
|
|
@@ -1052,7 +1221,24 @@ function printBye() {
|
|
|
1052
1221
|
case 'benchmark': case 'bench':
|
|
1053
1222
|
cmdBenchmark(); break;
|
|
1054
1223
|
case 'install':
|
|
1055
|
-
cmdInstall(rest);
|
|
1224
|
+
await cmdInstall(rest);
|
|
1225
|
+
process.exit(0);
|
|
1226
|
+
break;
|
|
1227
|
+
case 'update': {
|
|
1228
|
+
printSection('Update');
|
|
1229
|
+
console.log('');
|
|
1230
|
+
const upd = spawnSync(
|
|
1231
|
+
'npm', ['install', '-g', 'context-mcp-server@latest'],
|
|
1232
|
+
{ encoding: 'utf8', shell: true, stdio: 'inherit' },
|
|
1233
|
+
);
|
|
1234
|
+
if (upd.status === 0) {
|
|
1235
|
+
console.log(`\n ${ok('✓')} ${bold('context-mcp updated to latest')}`);
|
|
1236
|
+
} else {
|
|
1237
|
+
console.log(`\n ${bad('✗')} update failed — try: ${accent('npm i -g context-mcp-server@latest')}`);
|
|
1238
|
+
}
|
|
1239
|
+
console.log('');
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1056
1242
|
case 'online':
|
|
1057
1243
|
cmdOnline(rest); break;
|
|
1058
1244
|
case 'settings': case 'config':
|
|
@@ -1062,10 +1248,13 @@ function printBye() {
|
|
|
1062
1248
|
case 'delete': case 'del': case 'rm':
|
|
1063
1249
|
cmdDelete(rest); break;
|
|
1064
1250
|
case 'help': case '--help': case '-h':
|
|
1065
|
-
|
|
1251
|
+
await checkForUpdate();
|
|
1252
|
+
printUsage();
|
|
1253
|
+
break;
|
|
1066
1254
|
case '--version': case '-v':
|
|
1067
1255
|
console.log(pkg.version); break;
|
|
1068
1256
|
default:
|
|
1257
|
+
await checkForUpdate();
|
|
1069
1258
|
await interactive();
|
|
1070
1259
|
}
|
|
1071
1260
|
})();
|
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
|
```
|