context-mcp-server 1.1.0 → 1.1.1
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 +277 -86
- 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,136 @@ 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
|
+
// Hooks — write into the appropriate settings.json scope
|
|
599
|
+
// Project hooks live in .claude/hooks/ and are committed; user hooks in ~/.claude/hooks/
|
|
600
|
+
const hooksBase = scope === 'project' ? dir : homedir();
|
|
601
|
+
_copyHooks('claude', '.claude', hooksBase, [
|
|
602
|
+
'context-mcp-pre-tool-use.js',
|
|
603
|
+
'context-mcp-post-tool-use.js',
|
|
604
|
+
]);
|
|
605
|
+
const preHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-pre-tool-use.js');
|
|
606
|
+
const postHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-post-tool-use.js');
|
|
607
|
+
const settingsPath = scope === 'project'
|
|
608
|
+
? join(dir, '.claude', 'settings.json')
|
|
609
|
+
: join(homedir(), '.claude', 'settings.json');
|
|
610
|
+
_mergeHooksIntoSettings(settingsPath, {
|
|
611
|
+
PreToolUse: [{
|
|
612
|
+
matcher: 'Bash',
|
|
613
|
+
hooks: [{ type: 'command', command: preHook, timeout: 30, statusMessage: 'Checking shell command' }],
|
|
614
|
+
}],
|
|
615
|
+
PostToolUse: [{
|
|
616
|
+
matcher: 'Bash',
|
|
617
|
+
hooks: [{ type: 'command', command: postHook, timeout: 30, statusMessage: 'Saving failed shell context' }],
|
|
618
|
+
}],
|
|
619
|
+
}, scope === 'project' ? '.claude/settings.json' : '~/.claude/settings.json');
|
|
620
|
+
// Register MCP server via claude CLI
|
|
558
621
|
const scopeFlag = scope === 'global' ? 'user' : 'project';
|
|
559
622
|
const reg = spawnSync(
|
|
560
623
|
'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
@@ -571,17 +634,34 @@ const PLATFORMS = {
|
|
|
571
634
|
label: 'Cursor',
|
|
572
635
|
restartNote: 'Restart Cursor to load the new MCP server.',
|
|
573
636
|
install(dir, scope) {
|
|
637
|
+
// MCP config
|
|
574
638
|
const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
|
|
575
639
|
_writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
|
|
576
640
|
if (scope === 'project') {
|
|
577
|
-
|
|
641
|
+
// Rules
|
|
642
|
+
const mdc = _tpl('cursor/cursor-rules.mdc');
|
|
578
643
|
if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
|
|
644
|
+
// Slash commands
|
|
645
|
+
const cmdsSrc = join(TPLS, 'cursor', 'commands');
|
|
646
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
647
|
+
const src = join(cmdsSrc, file);
|
|
648
|
+
if (existsSync(src)) _writeFile(join(dir, '.cursor', 'commands', file), readFileSync(src, 'utf8'), `.cursor/commands/${file}`);
|
|
649
|
+
}
|
|
650
|
+
// Hook script
|
|
651
|
+
_copyHooks('cursor', '.cursor', dir, ['context-mcp-post-tool-use.js']);
|
|
652
|
+
const hookPath = join(dir, '.cursor', 'hooks', 'context-mcp-post-tool-use.js');
|
|
653
|
+
// Merge into .cursor/hooks.json (must keep "version": 1)
|
|
654
|
+
_mergeJsonFile(join(dir, '.cursor', 'hooks.json'), '.cursor/hooks.json', obj => {
|
|
655
|
+
obj.version = 1;
|
|
656
|
+
obj.hooks = obj.hooks || {};
|
|
657
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
658
|
+
obj.hooks.postToolUse = strip(obj.hooks.postToolUse).concat([
|
|
659
|
+
{ command: `node "${hookPath}"`, timeout: 30 },
|
|
660
|
+
]);
|
|
661
|
+
});
|
|
579
662
|
}
|
|
580
|
-
// Try to enable via cursor CLI
|
|
581
|
-
const reg = spawnSync(
|
|
582
|
-
'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
|
|
583
|
-
{ encoding: 'utf8', shell: true },
|
|
584
|
-
);
|
|
663
|
+
// Try to enable via cursor CLI
|
|
664
|
+
const reg = spawnSync('cursor', ['agent', 'mcp', 'enable', 'context-mcp'], { encoding: 'utf8', shell: true });
|
|
585
665
|
if (reg.status === 0) {
|
|
586
666
|
console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
|
|
587
667
|
}
|
|
@@ -590,21 +670,79 @@ const PLATFORMS = {
|
|
|
590
670
|
vscode: {
|
|
591
671
|
label: 'VS Code Copilot',
|
|
592
672
|
restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
|
|
593
|
-
install(dir) {
|
|
673
|
+
install(dir, scope) {
|
|
674
|
+
// MCP config
|
|
594
675
|
const mcpJson = JSON.stringify({
|
|
595
676
|
servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
|
|
596
677
|
}, null, 2);
|
|
597
678
|
_writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
|
|
679
|
+
if (scope === 'project') {
|
|
680
|
+
// Prompt files (.github/prompts/)
|
|
681
|
+
const cmdsSrc = join(TPLS, 'vscode', 'commands');
|
|
682
|
+
for (const file of ['context-resume.prompt.md', 'graph-build.prompt.md', 'save-context.prompt.md']) {
|
|
683
|
+
const src = join(cmdsSrc, file);
|
|
684
|
+
if (existsSync(src)) _writeFile(join(dir, '.github', 'prompts', file), readFileSync(src, 'utf8'), `.github/prompts/${file}`);
|
|
685
|
+
}
|
|
686
|
+
// Hook script
|
|
687
|
+
_copyHooks('vscode', '.vscode', dir, ['context-mcp-post-tool-use.js']);
|
|
688
|
+
const hookPath = join(dir, '.vscode', 'hooks', 'context-mcp-post-tool-use.js');
|
|
689
|
+
// Merge hooks into .vscode/settings.json under github.copilot.chat.agent.hooks
|
|
690
|
+
_mergeJsonFile(join(dir, '.vscode', 'settings.json'), '.vscode/settings.json', obj => {
|
|
691
|
+
const hooks = obj['github.copilot.chat.agent.hooks'] || {};
|
|
692
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
693
|
+
hooks.PostToolUse = strip(hooks.PostToolUse).concat([
|
|
694
|
+
{ type: 'command', command: `node "${hookPath}"`, timeout: 30, windows: `node "${hookPath}"` },
|
|
695
|
+
]);
|
|
696
|
+
obj['github.copilot.chat.agent.hooks'] = hooks;
|
|
697
|
+
});
|
|
698
|
+
}
|
|
598
699
|
},
|
|
599
700
|
},
|
|
600
701
|
gemini: {
|
|
601
702
|
label: 'Gemini CLI',
|
|
602
703
|
restartNote: 'Restart your Gemini CLI session.',
|
|
603
704
|
install(dir, scope) {
|
|
604
|
-
|
|
605
|
-
|
|
705
|
+
// MCP server config — merged into existing settings.json
|
|
706
|
+
const settingsPath = scope === 'project'
|
|
707
|
+
? join(dir, '.gemini', 'settings.json')
|
|
708
|
+
: join(homedir(), '.gemini', 'settings.json');
|
|
709
|
+
let settings = {};
|
|
710
|
+
try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
|
|
711
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
712
|
+
settings.mcpServers['context-mcp'] = MCP_SERVER_CMD;
|
|
713
|
+
// Hooks — write BeforeTool/AfterTool into settings.json
|
|
714
|
+
const hooksBase = scope === 'project' ? dir : homedir();
|
|
715
|
+
_copyHooks('gemini', '.gemini', hooksBase, [
|
|
716
|
+
'context-mcp-before-tool.js',
|
|
717
|
+
'context-mcp-after-tool.js',
|
|
718
|
+
]);
|
|
719
|
+
const beforeHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-before-tool.js');
|
|
720
|
+
const afterHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-after-tool.js');
|
|
721
|
+
settings.hooks = settings.hooks || {};
|
|
722
|
+
const stripOld = (arr) => (arr || []).filter(
|
|
723
|
+
g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')),
|
|
724
|
+
);
|
|
725
|
+
settings.hooks.BeforeTool = stripOld(settings.hooks.BeforeTool).concat([{
|
|
726
|
+
matcher: 'run_shell_command',
|
|
727
|
+
hooks: [{ type: 'command', command: `node ${beforeHook}`, timeout: 30 }],
|
|
728
|
+
}]);
|
|
729
|
+
settings.hooks.AfterTool = stripOld(settings.hooks.AfterTool).concat([{
|
|
730
|
+
matcher: 'run_shell_command',
|
|
731
|
+
hooks: [{ type: 'command', command: `node ${afterHook}`, timeout: 30 }],
|
|
732
|
+
}]);
|
|
733
|
+
_writeFile(settingsPath,
|
|
734
|
+
JSON.stringify(settings, null, 2),
|
|
735
|
+
scope === 'project' ? '.gemini/settings.json' : '~/.gemini/settings.json',
|
|
736
|
+
);
|
|
737
|
+
// Slash commands (.toml) — project-scoped only
|
|
606
738
|
if (scope === 'project') {
|
|
607
|
-
const
|
|
739
|
+
const cmdsSrc = join(TPLS, 'gemini', 'commands');
|
|
740
|
+
const cmdsDest = join(dir, '.gemini', 'commands');
|
|
741
|
+
for (const file of ['context-resume.toml', 'graph-build.toml', 'save-context.toml']) {
|
|
742
|
+
const src = join(cmdsSrc, file);
|
|
743
|
+
if (existsSync(src)) _writeFile(join(cmdsDest, file), readFileSync(src, 'utf8'), `.gemini/commands/${file}`);
|
|
744
|
+
}
|
|
745
|
+
const md = _tpl('gemini/GEMINI.md');
|
|
608
746
|
if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
|
|
609
747
|
}
|
|
610
748
|
},
|
|
@@ -613,12 +751,21 @@ const PLATFORMS = {
|
|
|
613
751
|
label: 'Codex CLI',
|
|
614
752
|
restartNote: 'Restart your Codex CLI session.',
|
|
615
753
|
install(dir, scope) {
|
|
616
|
-
const
|
|
617
|
-
|
|
754
|
+
const includeHooks = scope === 'project';
|
|
755
|
+
if (includeHooks) _copyCodexHooks(dir);
|
|
756
|
+
_writeFile(join(dir, '.codex', 'config.toml'), _codexConfigToml(dir, includeHooks), '.codex/config.toml');
|
|
618
757
|
if (scope === 'project') {
|
|
619
|
-
const md = _tpl('AGENTS.md');
|
|
758
|
+
const md = _tpl('codex/AGENTS.md');
|
|
620
759
|
if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
|
|
621
760
|
}
|
|
761
|
+
// Prompts (slash commands) — always user-global; Codex only loads ~/.codex/prompts/
|
|
762
|
+
const promptsSrc = join(TPLS, 'codex', 'prompts');
|
|
763
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
764
|
+
const src = join(promptsSrc, file);
|
|
765
|
+
if (existsSync(src)) {
|
|
766
|
+
_writeFile(join(homedir(), '.codex', 'prompts', file), readFileSync(src, 'utf8'), `~/.codex/prompts/${file}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
622
769
|
// Register via codex CLI so server is active immediately
|
|
623
770
|
const reg = spawnSync(
|
|
624
771
|
'codex', ['mcp', 'add', 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
|
|
@@ -634,15 +781,63 @@ const PLATFORMS = {
|
|
|
634
781
|
windsurf: {
|
|
635
782
|
label: 'Windsurf',
|
|
636
783
|
restartNote: 'Restart Windsurf to load the updated MCP config.',
|
|
637
|
-
install(dir) {
|
|
638
|
-
|
|
784
|
+
install(dir, scope) {
|
|
785
|
+
// Rules file
|
|
786
|
+
const rules = _tpl('windsurf/windsurf-rules.md');
|
|
639
787
|
if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
788
|
+
// Global MCP config (~/.codeium/windsurf/mcp_config.json)
|
|
789
|
+
_mergeJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'), '~/.codeium/windsurf/mcp_config.json', obj => {
|
|
790
|
+
obj.mcpServers = obj.mcpServers || {};
|
|
791
|
+
obj.mcpServers['context-mcp'] = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
|
|
792
|
+
});
|
|
793
|
+
if (scope === 'project') {
|
|
794
|
+
// Workflows (slash commands)
|
|
795
|
+
const wfSrc = join(TPLS, 'windsurf', 'workflows');
|
|
796
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
797
|
+
const src = join(wfSrc, file);
|
|
798
|
+
if (existsSync(src)) _writeFile(join(dir, '.windsurf', 'workflows', file), readFileSync(src, 'utf8'), `.windsurf/workflows/${file}`);
|
|
799
|
+
}
|
|
800
|
+
// Hook script + .windsurf/hooks.json
|
|
801
|
+
_copyHooks('windsurf', '.windsurf', dir, ['context-mcp-post-run-command.js']);
|
|
802
|
+
const hookPath = join(dir, '.windsurf', 'hooks', 'context-mcp-post-run-command.js');
|
|
803
|
+
_mergeJsonFile(join(dir, '.windsurf', 'hooks.json'), '.windsurf/hooks.json', obj => {
|
|
804
|
+
obj.hooks = obj.hooks || {};
|
|
805
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
806
|
+
obj.hooks.post_run_command = strip(obj.hooks.post_run_command).concat([{
|
|
807
|
+
command: `node "${hookPath}"`,
|
|
808
|
+
powershell: `node "${hookPath}"`,
|
|
809
|
+
show_output: false,
|
|
810
|
+
}]);
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
antigravity: {
|
|
816
|
+
label: 'Antigravity IDE',
|
|
817
|
+
restartNote: 'Restart your Antigravity session to pick up hooks and rules.',
|
|
818
|
+
install(dir, scope) {
|
|
819
|
+
// Antigravity uses stdio-incompatible MCP transport — integrate via ctx CLI + GEMINI.md instead.
|
|
820
|
+
if (scope === 'project') {
|
|
821
|
+
// Post-tool hook — saves failed tool calls to context-mcp via ctx CLI
|
|
822
|
+
_copyHooks('antigravity', '.agents', dir, ['context-mcp-post-tool-use.js']);
|
|
823
|
+
const hookPath = join(dir, '.agents', 'hooks', 'context-mcp-post-tool-use.js');
|
|
824
|
+
_mergeJsonFile(join(dir, '.agents', 'hooks.json'), '.agents/hooks.json', obj => {
|
|
825
|
+
const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
|
|
826
|
+
obj.hooks = strip(obj.hooks).concat([{
|
|
827
|
+
event: 'PostToolUse',
|
|
828
|
+
command: `node "${hookPath}"`,
|
|
829
|
+
}]);
|
|
830
|
+
});
|
|
831
|
+
// Workflows (slash commands) — .agents/workflows/
|
|
832
|
+
const wfSrc = join(TPLS, 'antigravity', 'workflows');
|
|
833
|
+
for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
|
|
834
|
+
const src = join(wfSrc, file);
|
|
835
|
+
if (existsSync(src)) _writeFile(join(dir, '.agents', 'workflows', file), readFileSync(src, 'utf8'), `.agents/workflows/${file}`);
|
|
836
|
+
}
|
|
837
|
+
// Rules file — Antigravity reads GEMINI.md at project root
|
|
838
|
+
const md = _tpl('antigravity/GEMINI.md');
|
|
839
|
+
if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
|
|
840
|
+
}
|
|
646
841
|
},
|
|
647
842
|
},
|
|
648
843
|
};
|
|
@@ -706,7 +901,7 @@ async function cmdInstall(args) {
|
|
|
706
901
|
|
|
707
902
|
if (!keys.length) {
|
|
708
903
|
printSection('Install');
|
|
709
|
-
console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--all]')}`);
|
|
904
|
+
console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--antigravity] [--all]')}`);
|
|
710
905
|
console.log('');
|
|
711
906
|
console.log(` ${accent('--initial ')} ${faint('Install / update Node.js + Python (codegraph) deps')}`);
|
|
712
907
|
console.log('');
|
|
@@ -1100,8 +1295,6 @@ async function interactive() {
|
|
|
1100
1295
|
clearScreen(); printCompactHeader('discussions'); cmdDiscussions(rest); break;
|
|
1101
1296
|
case 'summary':
|
|
1102
1297
|
clearScreen(); printCompactHeader('summary'); cmdSummary(rest); break;
|
|
1103
|
-
case 'stats':
|
|
1104
|
-
clearScreen(); printCompactHeader('stats'); cmdStats(); break;
|
|
1105
1298
|
case 'install':
|
|
1106
1299
|
clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
|
|
1107
1300
|
case 'online':
|
|
@@ -1166,8 +1359,6 @@ async function checkForUpdate() {
|
|
|
1166
1359
|
cmdDiscussions(rest); break;
|
|
1167
1360
|
case 'summary':
|
|
1168
1361
|
cmdSummary(rest); break;
|
|
1169
|
-
case 'stats':
|
|
1170
|
-
cmdStats(); break;
|
|
1171
1362
|
case 'install':
|
|
1172
1363
|
await cmdInstall(rest);
|
|
1173
1364
|
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
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Antigravity IDE PostToolUse hook for context-mcp.
|
|
5
|
+
*
|
|
6
|
+
* Input (stdin JSON): {
|
|
7
|
+
* toolCall: { name, args },
|
|
8
|
+
* stepIdx, conversationId, workspacePaths,
|
|
9
|
+
* error ← present when the tool invocation failed
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Saves failed tool invocations to context-mcp so the next session can see
|
|
13
|
+
* what broke. Exits silently when the tool succeeded or no error is present.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
|
|
18
|
+
process.stdin.resume();
|
|
19
|
+
process.stdin.setEncoding('utf8');
|
|
20
|
+
|
|
21
|
+
let input = '';
|
|
22
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
23
|
+
process.stdin.on('end', () => {
|
|
24
|
+
let event = {};
|
|
25
|
+
try {
|
|
26
|
+
event = input.trim() ? JSON.parse(input) : {};
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const toolCall = event.toolCall || {};
|
|
32
|
+
const toolName = toolCall.name || '';
|
|
33
|
+
const error = event.error;
|
|
34
|
+
|
|
35
|
+
if (!error || !toolName) return;
|
|
36
|
+
|
|
37
|
+
const args = toolCall.args || {};
|
|
38
|
+
const argsSnippet = Object.keys(args).length
|
|
39
|
+
? JSON.stringify(args).slice(0, 200)
|
|
40
|
+
: '';
|
|
41
|
+
|
|
42
|
+
const cwd = (event.workspacePaths || [])[0] || process.cwd();
|
|
43
|
+
const project = cwd.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop() || 'default';
|
|
44
|
+
|
|
45
|
+
const content = [
|
|
46
|
+
`Tool: ${toolName}`,
|
|
47
|
+
argsSnippet ? `Args: ${argsSnippet}` : null,
|
|
48
|
+
`Error: ${String(error).slice(0, 4000)}`,
|
|
49
|
+
].filter(Boolean).join('\n\n');
|
|
50
|
+
|
|
51
|
+
spawnSync('ctx', [
|
|
52
|
+
'save',
|
|
53
|
+
'--project', project,
|
|
54
|
+
'--type', 'bug',
|
|
55
|
+
'--title', `Failed tool: ${toolName.slice(0, 80)}`,
|
|
56
|
+
'--content', content,
|
|
57
|
+
], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
shell: true,
|
|
60
|
+
stdio: 'ignore',
|
|
61
|
+
});
|
|
62
|
+
});
|