context-mcp-server 1.1.0 → 1.1.2
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 +29 -7
- package/codegraph/__pycache__/affected.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/cache.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/callflow_html.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/export.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/report.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/server.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/tree_html.cpython-313.pyc +0 -0
- package/codegraph/affected.py +233 -0
- package/codegraph/cache.py +51 -2
- package/codegraph/callflow_html.py +273 -0
- package/codegraph/export.py +544 -0
- package/codegraph/extractors/__pycache__/ast_extractor.cpython-313.pyc +0 -0
- package/codegraph/extractors/ast_extractor.py +143 -16
- package/codegraph/graph/__pycache__/builder.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/clustering.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/query.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/symbol_resolution.cpython-313.pyc +0 -0
- package/codegraph/graph/builder.py +10 -0
- package/codegraph/graph/clustering.py +247 -10
- package/codegraph/graph/query.py +56 -0
- package/codegraph/graph/symbol_resolution.py +112 -0
- package/codegraph/report.py +53 -0
- package/codegraph/server.py +99 -10
- package/codegraph/tree_html.py +241 -0
- package/package.json +2 -2
- package/pyproject.toml +4 -1
- package/src/cli.js +303 -90
- package/src/server.js +7 -1
- package/src/templates/antigravity/GEMINI.md +96 -0
- package/src/templates/antigravity/hooks/context-mcp-post-tool-use.js +62 -0
- package/src/templates/antigravity/workflows/context-resume.md +20 -0
- package/src/templates/antigravity/workflows/graph-build.md +23 -0
- package/src/templates/antigravity/workflows/save-context.md +29 -0
- package/src/templates/{CLAUDE.md → claude/CLAUDE.md} +3 -0
- package/src/templates/claude/commands/graph-build.md +9 -0
- package/src/templates/claude/commands/save-context.md +19 -0
- package/src/templates/claude/hooks/context-mcp-post-tool-use.js +59 -0
- package/src/templates/claude/hooks/context-mcp-pre-tool-use.js +26 -0
- package/src/templates/{skills → claude/skills}/SKILL.md +3 -0
- package/src/templates/codex/AGENTS.md +107 -0
- package/src/templates/codex/hooks/context-mcp-post-tool-use.js +46 -0
- package/src/templates/codex/hooks/context-mcp-pre-tool-use.js +23 -0
- package/src/templates/codex/prompts/context-resume.md +15 -0
- package/src/templates/codex/prompts/graph-build.md +14 -0
- package/src/templates/codex/prompts/save-context.md +24 -0
- package/src/templates/cursor/commands/context-resume.md +7 -0
- package/src/templates/cursor/commands/graph-build.md +7 -0
- package/src/templates/cursor/commands/save-context.md +12 -0
- package/src/templates/{cursor-rules.mdc → cursor/cursor-rules.mdc} +13 -3
- package/src/templates/cursor/hooks/context-mcp-post-tool-use.js +55 -0
- package/src/templates/{GEMINI.md → gemini/GEMINI.md} +3 -1
- package/src/templates/gemini/commands/context-resume.toml +15 -0
- package/src/templates/gemini/commands/graph-build.toml +14 -0
- package/src/templates/gemini/commands/save-context.toml +24 -0
- package/src/templates/gemini/hooks/context-mcp-after-tool.js +59 -0
- package/src/templates/gemini/hooks/context-mcp-before-tool.js +26 -0
- package/src/templates/vscode/commands/context-resume.prompt.md +15 -0
- package/src/templates/vscode/commands/graph-build.prompt.md +10 -0
- package/src/templates/vscode/commands/save-context.prompt.md +16 -0
- package/src/templates/vscode/hooks/context-mcp-post-tool-use.js +58 -0
- package/src/templates/windsurf/hooks/context-mcp-post-run-command.js +57 -0
- package/src/templates/{windsurf-rules.md → windsurf/windsurf-rules.md} +6 -4
- package/src/templates/windsurf/workflows/context-resume.md +11 -0
- package/src/templates/windsurf/workflows/graph-build.md +11 -0
- package/src/templates/windsurf/workflows/save-context.md +18 -0
- package/src/tools/codegraph.js +37 -0
- package/uv.lock +1100 -3
- package/src/templates/AGENTS.md +0 -90
- package/src/templates/commands/graph-build.md +0 -5
- package/src/templates/commands/save-context.md +0 -12
- /package/src/templates/{commands → claude/commands}/context-resume.md +0 -0
package/src/cli.js
CHANGED
|
@@ -119,7 +119,6 @@ function printUsage() {
|
|
|
119
119
|
cmd('ctx summary [project]', 'summarize recent entries');
|
|
120
120
|
cmd('ctx projects', 'show all projects + graphs');
|
|
121
121
|
cmd('ctx discuss [project]', 'show discussions');
|
|
122
|
-
cmd('ctx stats', 'storage report: entries, types, graph status');
|
|
123
122
|
console.log('');
|
|
124
123
|
cmd('ctx install --initial', 'install / update Node.js + Python (codegraph) deps only');
|
|
125
124
|
cmd('ctx install --<platform>', 'write MCP config + skill/rules file only (no uv/npm)');
|
|
@@ -386,60 +385,6 @@ function cmdSummary(args) {
|
|
|
386
385
|
console.log('');
|
|
387
386
|
}
|
|
388
387
|
|
|
389
|
-
// ── Benchmark ────────────────────────────────────────────────────────────────
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
function cmdStats() {
|
|
393
|
-
const projects = listProjects();
|
|
394
|
-
const graphs = listGraphs();
|
|
395
|
-
const allEntries = getContext({ limit: 2000, compact: false });
|
|
396
|
-
|
|
397
|
-
printSection('Stats', 'context-mcp storage report');
|
|
398
|
-
|
|
399
|
-
// ── Projects ──────────────────────────────────────────────────────────────────
|
|
400
|
-
console.log(`\n ${bold(lblue('Projects'))}`);
|
|
401
|
-
if (!projects.length) {
|
|
402
|
-
console.log(` ${faint('no projects yet')}`);
|
|
403
|
-
} else {
|
|
404
|
-
for (const p of projects) {
|
|
405
|
-
const g = graphs.find(gr => {
|
|
406
|
-
const gp = (gr.path || '').replace(/\\/g, '/').replace(/\/$/, '').split('/').pop();
|
|
407
|
-
return gp === p.name;
|
|
408
|
-
});
|
|
409
|
-
const graphStatus = g ? ok(`graph: ${g.nodes} nodes, ${g.edges} edges`) : faint('no graph');
|
|
410
|
-
const builtAt = g?.builtAt ? faint(' built ' + g.builtAt.slice(0, 10)) : '';
|
|
411
|
-
console.log(` ${bold(p.name.padEnd(24))} ${muted(String(p.count).padStart(3) + ' entries')} ${graphStatus}${builtAt}`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ── Entry type breakdown ──────────────────────────────────────────────────────
|
|
416
|
-
console.log(`\n ${bold(lblue('Entries by type'))}`);
|
|
417
|
-
if (!allEntries.length) {
|
|
418
|
-
console.log(` ${faint('no entries yet')}`);
|
|
419
|
-
} else {
|
|
420
|
-
const byType = {};
|
|
421
|
-
for (const e of allEntries) { byType[e.type] = (byType[e.type] || 0) + 1; }
|
|
422
|
-
for (const [type, count] of Object.entries(byType).sort((a, b) => b[1] - a[1])) {
|
|
423
|
-
const bar = color(C.dblue, '█'.repeat(Math.min(count, 20))) + color(C.darkgray, '░'.repeat(Math.max(0, 20 - count)));
|
|
424
|
-
console.log(` ${type.padEnd(14)} ${bar} ${muted(count)}`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// ── Storage summary ───────────────────────────────────────────────────────────
|
|
429
|
-
const compactions = allEntries.filter(e => e.type === 'compaction').length;
|
|
430
|
-
const avgSize = allEntries.length
|
|
431
|
-
? Math.round(allEntries.reduce((s, e) => s + (e.content || '').length, 0) / allEntries.length)
|
|
432
|
-
: 0;
|
|
433
|
-
|
|
434
|
-
console.log(`\n ${bold(lblue('Storage'))}`);
|
|
435
|
-
console.log(` ${faint('total entries: ')} ${muted(allEntries.length)}`);
|
|
436
|
-
console.log(` ${faint('compactions: ')} ${muted(compactions)}`);
|
|
437
|
-
console.log(` ${faint('avg entry size: ')} ${muted(avgSize + ' chars')}`);
|
|
438
|
-
console.log(` ${faint('graphs built: ')} ${muted(graphs.length)}`);
|
|
439
|
-
console.log('');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
388
|
// ── Install ───────────────────────────────────────────────────────────────────
|
|
444
389
|
|
|
445
390
|
const TPLS = join(__dirname, 'templates');
|
|
@@ -469,6 +414,7 @@ const _GLOBAL_GITIGNORE_ENTRIES = [
|
|
|
469
414
|
'.gemini/',
|
|
470
415
|
'.codex/',
|
|
471
416
|
'.windsurf/',
|
|
417
|
+
'.agents/',
|
|
472
418
|
// Build outputs and session artifacts
|
|
473
419
|
'codegraph-cache/',
|
|
474
420
|
'.mcp.json',
|
|
@@ -483,6 +429,7 @@ function _graphForProject(graphs, projectName) {
|
|
|
483
429
|
|
|
484
430
|
const _PROJECT_GITIGNORE_ENTRIES = [
|
|
485
431
|
'.claude/', '.cursor/', '.vscode/', '.gemini/', '.codex/',
|
|
432
|
+
'.agents/',
|
|
486
433
|
'codegraph-cache/', '.mcp.json', 'CLAUDE.md', 'GEMINI.md', 'AGENTS.md',
|
|
487
434
|
];
|
|
488
435
|
|
|
@@ -525,7 +472,7 @@ function _updateGlobalGitignore() {
|
|
|
525
472
|
}
|
|
526
473
|
|
|
527
474
|
function _writeCommands(baseDir) {
|
|
528
|
-
const cmdsDir = join(TPLS, 'commands');
|
|
475
|
+
const cmdsDir = join(TPLS, 'claude', 'commands');
|
|
529
476
|
const destDir = join(baseDir, '.claude', 'commands');
|
|
530
477
|
for (const [name, label] of [
|
|
531
478
|
['context-resume.md', '/context-resume'],
|
|
@@ -541,20 +488,144 @@ function _writeCommands(baseDir) {
|
|
|
541
488
|
|
|
542
489
|
const MCP_SERVER_CMD = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
|
|
543
490
|
|
|
491
|
+
function _tomlString(value) {
|
|
492
|
+
return JSON.stringify(value);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function _copyHooks(platform, dotDir, dir, hookFiles) {
|
|
496
|
+
const hooksSrc = join(TPLS, platform, 'hooks');
|
|
497
|
+
const hooksDest = join(dir, dotDir, 'hooks');
|
|
498
|
+
for (const file of hookFiles) {
|
|
499
|
+
const src = join(hooksSrc, file);
|
|
500
|
+
if (existsSync(src)) {
|
|
501
|
+
_writeFile(join(hooksDest, file), readFileSync(src, 'utf8'), `${dotDir}/hooks/${file}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function _copyCodexHooks(dir) {
|
|
507
|
+
_copyHooks('codex', '.codex', dir, [
|
|
508
|
+
'context-mcp-pre-tool-use.js',
|
|
509
|
+
'context-mcp-post-tool-use.js',
|
|
510
|
+
]);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Read JSON from path (returns {} on missing/invalid), run mutateFn, write back.
|
|
514
|
+
function _mergeJsonFile(filePath, label, mutateFn) {
|
|
515
|
+
let obj = {};
|
|
516
|
+
try { obj = JSON.parse(readFileSync(filePath, 'utf8')); } catch {}
|
|
517
|
+
mutateFn(obj);
|
|
518
|
+
_writeFile(filePath, JSON.stringify(obj, null, 2), label);
|
|
519
|
+
return obj;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Merge `entries` ({ EventName: [hookGroup] }) into an existing settings.json
|
|
523
|
+
// hooks object, replacing any previously installed context-mcp groups.
|
|
524
|
+
function _mergeHooksIntoSettings(settingsPath, entries, label) {
|
|
525
|
+
let settings = {};
|
|
526
|
+
try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
|
|
527
|
+
settings.hooks = settings.hooks || {};
|
|
528
|
+
for (const [event, groups] of Object.entries(entries)) {
|
|
529
|
+
const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
530
|
+
settings.hooks[event] = existing
|
|
531
|
+
.filter(g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')))
|
|
532
|
+
.concat(groups);
|
|
533
|
+
}
|
|
534
|
+
_writeFile(settingsPath, JSON.stringify(settings, null, 2), label);
|
|
535
|
+
return settings;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function _codexConfigToml(dir, includeHooks = false) {
|
|
539
|
+
const lines = [
|
|
540
|
+
'[mcp_servers.context-mcp]',
|
|
541
|
+
'command = "npx"',
|
|
542
|
+
'args = ["-y", "context-mcp-server@latest"]',
|
|
543
|
+
'default_tools_approval_mode = "prompt"',
|
|
544
|
+
'',
|
|
545
|
+
'[mcp_servers.context-mcp.tools.context]',
|
|
546
|
+
'approval_mode = "approve"',
|
|
547
|
+
'',
|
|
548
|
+
'[mcp_servers.context-mcp.tools.search]',
|
|
549
|
+
'approval_mode = "approve"',
|
|
550
|
+
'',
|
|
551
|
+
'[mcp_servers.context-mcp.tools.codegraph_query]',
|
|
552
|
+
'approval_mode = "approve"',
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
if (includeHooks) {
|
|
556
|
+
const preHook = join(dir, '.codex', 'hooks', 'context-mcp-pre-tool-use.js');
|
|
557
|
+
const postHook = join(dir, '.codex', 'hooks', 'context-mcp-post-tool-use.js');
|
|
558
|
+
lines.push(
|
|
559
|
+
'',
|
|
560
|
+
'[[hooks.PreToolUse]]',
|
|
561
|
+
'matcher = "^Bash$"',
|
|
562
|
+
'',
|
|
563
|
+
'[[hooks.PreToolUse.hooks]]',
|
|
564
|
+
'type = "command"',
|
|
565
|
+
`command = ${_tomlString(`node ${_tomlString(preHook)}`)}`,
|
|
566
|
+
`command_windows = ${_tomlString(`node ${_tomlString(preHook)}`)}`,
|
|
567
|
+
'timeout = 30',
|
|
568
|
+
'statusMessage = "Checking shell command"',
|
|
569
|
+
'',
|
|
570
|
+
'[[hooks.PostToolUse]]',
|
|
571
|
+
'matcher = "^Bash$"',
|
|
572
|
+
'',
|
|
573
|
+
'[[hooks.PostToolUse.hooks]]',
|
|
574
|
+
'type = "command"',
|
|
575
|
+
`command = ${_tomlString(`node ${_tomlString(postHook)}`)}`,
|
|
576
|
+
`command_windows = ${_tomlString(`node ${_tomlString(postHook)}`)}`,
|
|
577
|
+
'timeout = 30',
|
|
578
|
+
'statusMessage = "Saving failed shell context"',
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return `${lines.join('\n')}\n`;
|
|
583
|
+
}
|
|
584
|
+
|
|
544
585
|
const PLATFORMS = {
|
|
545
586
|
claude: {
|
|
546
587
|
label: 'Claude Code',
|
|
547
588
|
restartNote: 'Type /context-resume in Claude Code to start using context-mcp.',
|
|
548
589
|
install(dir, scope) {
|
|
549
|
-
//
|
|
550
|
-
const skillSrc = join(TPLS, 'skills', 'SKILL.md');
|
|
590
|
+
// Skill — user-global, works across all projects
|
|
591
|
+
const skillSrc = join(TPLS, 'claude', 'skills', 'SKILL.md');
|
|
551
592
|
const skillDest = join(homedir(), '.claude', 'skills', 'context-mcp', 'SKILL.md');
|
|
552
593
|
if (existsSync(skillSrc)) {
|
|
553
594
|
_writeFile(skillDest, readFileSync(skillSrc, 'utf8'), '~/.claude/skills/context-mcp/');
|
|
554
595
|
}
|
|
555
|
-
// Slash commands
|
|
596
|
+
// Slash commands — user-global
|
|
556
597
|
_writeCommands(homedir());
|
|
557
|
-
//
|
|
598
|
+
// Rules file — project root for project scope, ~/.claude/CLAUDE.md for global
|
|
599
|
+
const claudeMd = _tpl('claude/CLAUDE.md');
|
|
600
|
+
if (claudeMd) {
|
|
601
|
+
const claudeMdPath = scope === 'project'
|
|
602
|
+
? join(dir, 'CLAUDE.md')
|
|
603
|
+
: join(homedir(), '.claude', 'CLAUDE.md');
|
|
604
|
+
_writeFile(claudeMdPath, claudeMd, scope === 'project' ? 'CLAUDE.md' : '~/.claude/CLAUDE.md');
|
|
605
|
+
}
|
|
606
|
+
// Hooks — write into the appropriate settings.json scope
|
|
607
|
+
// Project hooks live in .claude/hooks/ and are committed; user hooks in ~/.claude/hooks/
|
|
608
|
+
const hooksBase = scope === 'project' ? dir : homedir();
|
|
609
|
+
_copyHooks('claude', '.claude', hooksBase, [
|
|
610
|
+
'context-mcp-pre-tool-use.js',
|
|
611
|
+
'context-mcp-post-tool-use.js',
|
|
612
|
+
]);
|
|
613
|
+
const preHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-pre-tool-use.js');
|
|
614
|
+
const postHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-post-tool-use.js');
|
|
615
|
+
const settingsPath = scope === 'project'
|
|
616
|
+
? join(dir, '.claude', 'settings.json')
|
|
617
|
+
: join(homedir(), '.claude', 'settings.json');
|
|
618
|
+
_mergeHooksIntoSettings(settingsPath, {
|
|
619
|
+
PreToolUse: [{
|
|
620
|
+
matcher: 'Bash',
|
|
621
|
+
hooks: [{ type: 'command', command: preHook, timeout: 30, statusMessage: 'Checking shell command' }],
|
|
622
|
+
}],
|
|
623
|
+
PostToolUse: [{
|
|
624
|
+
matcher: 'Bash',
|
|
625
|
+
hooks: [{ type: 'command', command: postHook, timeout: 30, statusMessage: 'Saving failed shell context' }],
|
|
626
|
+
}],
|
|
627
|
+
}, scope === 'project' ? '.claude/settings.json' : '~/.claude/settings.json');
|
|
628
|
+
// Register MCP server via claude CLI
|
|
558
629
|
const scopeFlag = scope === 'global' ? 'user' : 'project';
|
|
559
630
|
const reg = spawnSync(
|
|
560
631
|
'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
@@ -571,17 +642,34 @@ const PLATFORMS = {
|
|
|
571
642
|
label: 'Cursor',
|
|
572
643
|
restartNote: 'Restart Cursor to load the new MCP server.',
|
|
573
644
|
install(dir, scope) {
|
|
645
|
+
// MCP config
|
|
574
646
|
const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
575
647
|
_writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
|
|
576
648
|
if (scope === 'project') {
|
|
577
|
-
|
|
649
|
+
// Rules
|
|
650
|
+
const mdc = _tpl('cursor/cursor-rules.mdc');
|
|
578
651
|
if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
|
|
652
|
+
// Slash commands
|
|
653
|
+
const cmdsSrc = join(TPLS, 'cursor', 'commands');
|
|
654
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
655
|
+
const src = join(cmdsSrc, file);
|
|
656
|
+
if (existsSync(src)) _writeFile(join(dir, '.cursor', 'commands', file), readFileSync(src, 'utf8'), `.cursor/commands/${file}`);
|
|
657
|
+
}
|
|
658
|
+
// Hook script
|
|
659
|
+
_copyHooks('cursor', '.cursor', dir, ['context-mcp-post-tool-use.js']);
|
|
660
|
+
const hookPath = join(dir, '.cursor', 'hooks', 'context-mcp-post-tool-use.js');
|
|
661
|
+
// Merge into .cursor/hooks.json (must keep "version": 1)
|
|
662
|
+
_mergeJsonFile(join(dir, '.cursor', 'hooks.json'), '.cursor/hooks.json', obj => {
|
|
663
|
+
obj.version = 1;
|
|
664
|
+
obj.hooks = obj.hooks || {};
|
|
665
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
666
|
+
obj.hooks.postToolUse = strip(obj.hooks.postToolUse).concat([
|
|
667
|
+
{ command: `node "${hookPath}"`, timeout: 30 },
|
|
668
|
+
]);
|
|
669
|
+
});
|
|
579
670
|
}
|
|
580
|
-
// Try to enable via cursor CLI
|
|
581
|
-
const reg = spawnSync(
|
|
582
|
-
'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
|
|
583
|
-
{ encoding: 'utf8', shell: true },
|
|
584
|
-
);
|
|
671
|
+
// Try to enable via cursor CLI
|
|
672
|
+
const reg = spawnSync('cursor', ['agent', 'mcp', 'enable', 'context-mcp'], { encoding: 'utf8', shell: true });
|
|
585
673
|
if (reg.status === 0) {
|
|
586
674
|
console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
|
|
587
675
|
}
|
|
@@ -590,22 +678,85 @@ const PLATFORMS = {
|
|
|
590
678
|
vscode: {
|
|
591
679
|
label: 'VS Code Copilot',
|
|
592
680
|
restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
|
|
593
|
-
install(dir) {
|
|
681
|
+
install(dir, scope) {
|
|
682
|
+
// MCP config
|
|
594
683
|
const mcpJson = JSON.stringify({
|
|
595
684
|
servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
|
|
596
685
|
}, null, 2);
|
|
597
686
|
_writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
|
|
687
|
+
if (scope === 'project') {
|
|
688
|
+
// Prompt files (.github/prompts/)
|
|
689
|
+
const cmdsSrc = join(TPLS, 'vscode', 'commands');
|
|
690
|
+
for (const file of ['context-resume.prompt.md', 'graph-build.prompt.md', 'save-context.prompt.md']) {
|
|
691
|
+
const src = join(cmdsSrc, file);
|
|
692
|
+
if (existsSync(src)) _writeFile(join(dir, '.github', 'prompts', file), readFileSync(src, 'utf8'), `.github/prompts/${file}`);
|
|
693
|
+
}
|
|
694
|
+
// Hook script
|
|
695
|
+
_copyHooks('vscode', '.vscode', dir, ['context-mcp-post-tool-use.js']);
|
|
696
|
+
const hookPath = join(dir, '.vscode', 'hooks', 'context-mcp-post-tool-use.js');
|
|
697
|
+
// Merge hooks into .vscode/settings.json under github.copilot.chat.agent.hooks
|
|
698
|
+
_mergeJsonFile(join(dir, '.vscode', 'settings.json'), '.vscode/settings.json', obj => {
|
|
699
|
+
const hooks = obj['github.copilot.chat.agent.hooks'] || {};
|
|
700
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
701
|
+
hooks.PostToolUse = strip(hooks.PostToolUse).concat([
|
|
702
|
+
{ type: 'command', command: `node "${hookPath}"`, timeout: 30, windows: `node "${hookPath}"` },
|
|
703
|
+
]);
|
|
704
|
+
obj['github.copilot.chat.agent.hooks'] = hooks;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
598
707
|
},
|
|
599
708
|
},
|
|
600
709
|
gemini: {
|
|
601
710
|
label: 'Gemini CLI',
|
|
602
711
|
restartNote: 'Restart your Gemini CLI session.',
|
|
603
712
|
install(dir, scope) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
713
|
+
// MCP server config — merged into existing settings.json
|
|
714
|
+
const settingsPath = scope === 'project'
|
|
715
|
+
? join(dir, '.gemini', 'settings.json')
|
|
716
|
+
: join(homedir(), '.gemini', 'settings.json');
|
|
717
|
+
let settings = {};
|
|
718
|
+
try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
|
|
719
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
720
|
+
settings.mcpServers['context-mcp'] = MCP_SERVER_CMD;
|
|
721
|
+
// Hooks — write BeforeTool/AfterTool into settings.json
|
|
722
|
+
const hooksBase = scope === 'project' ? dir : homedir();
|
|
723
|
+
_copyHooks('gemini', '.gemini', hooksBase, [
|
|
724
|
+
'context-mcp-before-tool.js',
|
|
725
|
+
'context-mcp-after-tool.js',
|
|
726
|
+
]);
|
|
727
|
+
const beforeHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-before-tool.js');
|
|
728
|
+
const afterHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-after-tool.js');
|
|
729
|
+
settings.hooks = settings.hooks || {};
|
|
730
|
+
const stripOld = (arr) => (arr || []).filter(
|
|
731
|
+
g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')),
|
|
732
|
+
);
|
|
733
|
+
settings.hooks.BeforeTool = stripOld(settings.hooks.BeforeTool).concat([{
|
|
734
|
+
matcher: 'run_shell_command',
|
|
735
|
+
hooks: [{ type: 'command', command: `node ${beforeHook}`, timeout: 30 }],
|
|
736
|
+
}]);
|
|
737
|
+
settings.hooks.AfterTool = stripOld(settings.hooks.AfterTool).concat([{
|
|
738
|
+
matcher: 'run_shell_command',
|
|
739
|
+
hooks: [{ type: 'command', command: `node ${afterHook}`, timeout: 30 }],
|
|
740
|
+
}]);
|
|
741
|
+
_writeFile(settingsPath,
|
|
742
|
+
JSON.stringify(settings, null, 2),
|
|
743
|
+
scope === 'project' ? '.gemini/settings.json' : '~/.gemini/settings.json',
|
|
744
|
+
);
|
|
745
|
+
// Slash commands (.toml) — project: .gemini/commands/, global: ~/.gemini/commands/
|
|
746
|
+
const geminiCmdsDest = join(hooksBase, '.gemini', 'commands');
|
|
747
|
+
const cmdsSrc = join(TPLS, 'gemini', 'commands');
|
|
748
|
+
for (const file of ['context-resume.toml', 'graph-build.toml', 'save-context.toml']) {
|
|
749
|
+
const src = join(cmdsSrc, file);
|
|
750
|
+
const label = scope === 'project' ? `.gemini/commands/${file}` : `~/.gemini/commands/${file}`;
|
|
751
|
+
if (existsSync(src)) _writeFile(join(geminiCmdsDest, file), readFileSync(src, 'utf8'), label);
|
|
752
|
+
}
|
|
753
|
+
// Rules file — project root for project scope, ~/.gemini/GEMINI.md for global
|
|
754
|
+
const geminiMd = _tpl('gemini/GEMINI.md');
|
|
755
|
+
if (geminiMd) {
|
|
756
|
+
const geminiMdPath = scope === 'project'
|
|
757
|
+
? join(dir, 'GEMINI.md')
|
|
758
|
+
: join(homedir(), '.gemini', 'GEMINI.md');
|
|
759
|
+
_writeFile(geminiMdPath, geminiMd, scope === 'project' ? 'GEMINI.md' : '~/.gemini/GEMINI.md');
|
|
609
760
|
}
|
|
610
761
|
},
|
|
611
762
|
},
|
|
@@ -613,11 +764,24 @@ const PLATFORMS = {
|
|
|
613
764
|
label: 'Codex CLI',
|
|
614
765
|
restartNote: 'Restart your Codex CLI session.',
|
|
615
766
|
install(dir, scope) {
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
767
|
+
const includeHooks = scope === 'project';
|
|
768
|
+
if (includeHooks) _copyCodexHooks(dir);
|
|
769
|
+
_writeFile(join(dir, '.codex', 'config.toml'), _codexConfigToml(dir, includeHooks), '.codex/config.toml');
|
|
770
|
+
// Rules file — project root for project scope, ~/.codex/AGENTS.md for global
|
|
771
|
+
const codexMd = _tpl('codex/AGENTS.md');
|
|
772
|
+
if (codexMd) {
|
|
773
|
+
const codexMdPath = scope === 'project'
|
|
774
|
+
? join(dir, 'AGENTS.md')
|
|
775
|
+
: join(homedir(), '.codex', 'AGENTS.md');
|
|
776
|
+
_writeFile(codexMdPath, codexMd, scope === 'project' ? 'AGENTS.md' : '~/.codex/AGENTS.md');
|
|
777
|
+
}
|
|
778
|
+
// Prompts (slash commands) — always user-global; Codex only loads ~/.codex/prompts/
|
|
779
|
+
const promptsSrc = join(TPLS, 'codex', 'prompts');
|
|
780
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
781
|
+
const src = join(promptsSrc, file);
|
|
782
|
+
if (existsSync(src)) {
|
|
783
|
+
_writeFile(join(homedir(), '.codex', 'prompts', file), readFileSync(src, 'utf8'), `~/.codex/prompts/${file}`);
|
|
784
|
+
}
|
|
621
785
|
}
|
|
622
786
|
// Register via codex CLI so server is active immediately
|
|
623
787
|
const reg = spawnSync(
|
|
@@ -634,15 +798,68 @@ const PLATFORMS = {
|
|
|
634
798
|
windsurf: {
|
|
635
799
|
label: 'Windsurf',
|
|
636
800
|
restartNote: 'Restart Windsurf to load the updated MCP config.',
|
|
637
|
-
install(dir) {
|
|
638
|
-
|
|
801
|
+
install(dir, scope) {
|
|
802
|
+
// Rules file
|
|
803
|
+
const rules = _tpl('windsurf/windsurf-rules.md');
|
|
639
804
|
if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
805
|
+
// Global MCP config (~/.codeium/windsurf/mcp_config.json)
|
|
806
|
+
_mergeJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'), '~/.codeium/windsurf/mcp_config.json', obj => {
|
|
807
|
+
obj.mcpServers = obj.mcpServers || {};
|
|
808
|
+
obj.mcpServers['context-mcp'] = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
|
|
809
|
+
});
|
|
810
|
+
if (scope === 'project') {
|
|
811
|
+
// Workflows (slash commands)
|
|
812
|
+
const wfSrc = join(TPLS, 'windsurf', 'workflows');
|
|
813
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
814
|
+
const src = join(wfSrc, file);
|
|
815
|
+
if (existsSync(src)) _writeFile(join(dir, '.windsurf', 'workflows', file), readFileSync(src, 'utf8'), `.windsurf/workflows/${file}`);
|
|
816
|
+
}
|
|
817
|
+
// Hook script + .windsurf/hooks.json
|
|
818
|
+
_copyHooks('windsurf', '.windsurf', dir, ['context-mcp-post-run-command.js']);
|
|
819
|
+
const hookPath = join(dir, '.windsurf', 'hooks', 'context-mcp-post-run-command.js');
|
|
820
|
+
_mergeJsonFile(join(dir, '.windsurf', 'hooks.json'), '.windsurf/hooks.json', obj => {
|
|
821
|
+
obj.hooks = obj.hooks || {};
|
|
822
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
823
|
+
obj.hooks.post_run_command = strip(obj.hooks.post_run_command).concat([{
|
|
824
|
+
command: `node "${hookPath}"`,
|
|
825
|
+
powershell: `node "${hookPath}"`,
|
|
826
|
+
show_output: false,
|
|
827
|
+
}]);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
antigravity: {
|
|
833
|
+
label: 'Antigravity IDE',
|
|
834
|
+
restartNote: 'Restart your Antigravity session to pick up hooks and rules.',
|
|
835
|
+
install(dir, scope) {
|
|
836
|
+
// Antigravity uses stdio-incompatible MCP transport — integrate via ctx CLI + GEMINI.md instead.
|
|
837
|
+
if (scope === 'project') {
|
|
838
|
+
// Post-tool hook — saves failed tool calls to context-mcp via ctx CLI
|
|
839
|
+
_copyHooks('antigravity', '.agents', dir, ['context-mcp-post-tool-use.js']);
|
|
840
|
+
const hookPath = join(dir, '.agents', 'hooks', 'context-mcp-post-tool-use.js');
|
|
841
|
+
_mergeJsonFile(join(dir, '.agents', 'hooks.json'), '.agents/hooks.json', obj => {
|
|
842
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
843
|
+
obj.hooks = strip(obj.hooks).concat([{
|
|
844
|
+
event: 'PostToolUse',
|
|
845
|
+
command: `node "${hookPath}"`,
|
|
846
|
+
}]);
|
|
847
|
+
});
|
|
848
|
+
// Workflows (slash commands) — .agents/workflows/
|
|
849
|
+
const wfSrc = join(TPLS, 'antigravity', 'workflows');
|
|
850
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
851
|
+
const src = join(wfSrc, file);
|
|
852
|
+
if (existsSync(src)) _writeFile(join(dir, '.agents', 'workflows', file), readFileSync(src, 'utf8'), `.agents/workflows/${file}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Rules file — project root for project scope, ~/.config/antigravity/GEMINI.md for global
|
|
856
|
+
const agMd = _tpl('antigravity/GEMINI.md');
|
|
857
|
+
if (agMd) {
|
|
858
|
+
const agMdPath = scope === 'project'
|
|
859
|
+
? join(dir, 'GEMINI.md')
|
|
860
|
+
: join(homedir(), '.config', 'antigravity', 'GEMINI.md');
|
|
861
|
+
_writeFile(agMdPath, agMd, scope === 'project' ? 'GEMINI.md' : '~/.config/antigravity/GEMINI.md');
|
|
862
|
+
}
|
|
646
863
|
},
|
|
647
864
|
},
|
|
648
865
|
};
|
|
@@ -706,7 +923,7 @@ async function cmdInstall(args) {
|
|
|
706
923
|
|
|
707
924
|
if (!keys.length) {
|
|
708
925
|
printSection('Install');
|
|
709
|
-
console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--all]')}`);
|
|
926
|
+
console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--antigravity] [--all]')}`);
|
|
710
927
|
console.log('');
|
|
711
928
|
console.log(` ${accent('--initial ')} ${faint('Install / update Node.js + Python (codegraph) deps')}`);
|
|
712
929
|
console.log('');
|
|
@@ -1100,8 +1317,6 @@ async function interactive() {
|
|
|
1100
1317
|
clearScreen(); printCompactHeader('discussions'); cmdDiscussions(rest); break;
|
|
1101
1318
|
case 'summary':
|
|
1102
1319
|
clearScreen(); printCompactHeader('summary'); cmdSummary(rest); break;
|
|
1103
|
-
case 'stats':
|
|
1104
|
-
clearScreen(); printCompactHeader('stats'); cmdStats(); break;
|
|
1105
1320
|
case 'install':
|
|
1106
1321
|
clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
|
|
1107
1322
|
case 'online':
|
|
@@ -1166,8 +1381,6 @@ async function checkForUpdate() {
|
|
|
1166
1381
|
cmdDiscussions(rest); break;
|
|
1167
1382
|
case 'summary':
|
|
1168
1383
|
cmdSummary(rest); break;
|
|
1169
|
-
case 'stats':
|
|
1170
|
-
cmdStats(); break;
|
|
1171
1384
|
case 'install':
|
|
1172
1385
|
await cmdInstall(rest);
|
|
1173
1386
|
process.exit(0);
|
package/src/server.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
|
|
4
7
|
import { getConfig } from './config.js';
|
|
5
8
|
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const { version } = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
11
|
+
|
|
6
12
|
import * as contextTool from './tools/context.js';
|
|
7
13
|
import * as searchTool from './tools/search.js';
|
|
8
14
|
import * as planTool from './tools/plan.js';
|
|
@@ -23,7 +29,7 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
|
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
const server = new Server(
|
|
26
|
-
{ name: 'context-mcp', version
|
|
32
|
+
{ name: 'context-mcp', version },
|
|
27
33
|
{ capabilities: { tools: {} } }
|
|
28
34
|
);
|
|
29
35
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Context-MCP — Antigravity IDE Usage Guide
|
|
2
|
+
|
|
3
|
+
Persistent memory + codebase knowledge graph via `ctx` CLI commands.
|
|
4
|
+
Run `ctx resume` to start every session. Use `ctx search` to recall past work. Use `ctx save` to persist decisions. Use `ctx add` to record discoveries.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Start of Every Conversation (MANDATORY)
|
|
9
|
+
|
|
10
|
+
Run `ctx resume --project <project>` (shell command) before anything else.
|
|
11
|
+
|
|
12
|
+
Returns recent entries, active plans, graph status.
|
|
13
|
+
|
|
14
|
+
- Graph built → use `ctx graph query` / `ctx graph arch` before reading files
|
|
15
|
+
- Graph not built → run `ctx graph build <path>` first
|
|
16
|
+
- `totalEntries ≥ 20` → save compaction summary FIRST (see Rule 4)
|
|
17
|
+
- Active plans present → read them before starting new work
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Save Triggers (MANDATORY)
|
|
22
|
+
|
|
23
|
+
Run `ctx save` after finishing anything worth keeping:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
ctx save --project <project> --title "..." --why "..." --outcome "..." --type note
|
|
27
|
+
ctx save --project <project> --title "..." --content "..." --type bug
|
|
28
|
+
ctx save --project <project> --title "Session summary — YYYY-MM-DD" --content "..." --type compaction
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Trigger | Command |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Task / fix / feature complete | `ctx save` with `--why` + `--outcome` + `--files` |
|
|
34
|
+
| Decision made | `ctx save` with `--why` + `--outcome` |
|
|
35
|
+
| Discovery / constraint | `ctx save` with `--content` |
|
|
36
|
+
| User says "save this" | `ctx save` with `--title` + `--content` |
|
|
37
|
+
| Compact memory | `ctx save --type compaction` |
|
|
38
|
+
|
|
39
|
+
**Do NOT save:** routine reads, search results, explanations of existing code.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Plans (MANDATORY for multi-file work)
|
|
44
|
+
|
|
45
|
+
**Create a plan when:** editing 2+ files, multi-step implementation, refactor, multi-file bug fix.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
ctx plan save --project <project> --name "..." --content "..."
|
|
49
|
+
ctx plan done --project <project> --name "..." # deletes the plan
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Check active plans on resume — don't create duplicates.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 4. Auto-Summary at ≥ 20 Entries (MANDATORY)
|
|
57
|
+
|
|
58
|
+
When `totalEntries ≥ 20`, run this BEFORE the user's task:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
ctx save --project <project> --type compaction \
|
|
62
|
+
--title "Session summary — YYYY-MM-DD" \
|
|
63
|
+
--content "<what was built, decided, broke, current state>"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 5. Search Before Asking
|
|
69
|
+
|
|
70
|
+
Run `ctx search "<query>" --project <project>` before asking the user to re-explain past work.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 6. ContextGraph CLI
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
ctx graph build <path> → build AST graph (run once, incremental)
|
|
78
|
+
ctx graph arch <path> → module map: files, exports, imports
|
|
79
|
+
ctx graph query <path> "<question>" → structural question about the codebase
|
|
80
|
+
ctx graph nodes <path> <type> → list all nodes of a type
|
|
81
|
+
ctx graph report <path> → god nodes, clusters, surprising connections
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use `ctx graph arch` first. Never read files for structure questions.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 7. Rules
|
|
89
|
+
|
|
90
|
+
1. `ctx resume` first — before any tool or response
|
|
91
|
+
2. Always pass `--project`
|
|
92
|
+
3. Save on task complete — `--why` + `--outcome` + `--files`
|
|
93
|
+
4. Compaction at ≥ 20 entries — before starting task
|
|
94
|
+
5. Plan before multi-file work — `ctx plan done` deletes it
|
|
95
|
+
6. `ctx search` before asking about past work
|
|
96
|
+
7. `ctx graph` before reading files
|