klypix-mcp 1.34.0 → 1.37.0
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 +47 -22
- package/bin/klypix-install.mjs +83 -20
- package/bin/klypix-link.mjs +11 -3
- package/bin/klypix-mcp.mjs +82 -8
- package/package.json +4 -3
- package/src/agent-presence.mjs +102 -6
- package/src/agent-rules.mjs +50 -6
- package/src/brain-doctor.mjs +33 -14
- package/src/codex-brain-hook.mjs +4 -1
- package/src/codex-hooks.mjs +67 -1
- package/src/global-brain-hook.mjs +6 -3
- package/src/klypix-core.mjs +96 -1
- package/src/mcp-presence.mjs +438 -0
package/README.md
CHANGED
|
@@ -10,18 +10,33 @@ You've seen the setup: point an AI at a folder of notes, watch the graph fill up
|
|
|
10
10
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
|
-
**Claude Code + Codex
|
|
13
|
+
**Claude Code + Codex:**
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npx klypix-mcp install
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Claude Code keeps its live hooks for auto-brief + auto-capture. Codex gets
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
Claude Code keeps its live hooks for auto-brief + auto-capture. Codex gets native MCP
|
|
20
|
+
tools, **automatic live-session presence**, and approval-free **smart task synchronization**
|
|
21
|
+
from the authorized MCP connection itself. `brain_sync` is a bounded **Context Gateway**:
|
|
22
|
+
one call returns task-relevant brain memory, meaningful active-task peers, structured
|
|
23
|
+
exact-file conflicts, one-time notes, and automatic late-arrival overlap alerts. Idle MCP
|
|
24
|
+
connections stay counted for diagnostics but are hidden from the task-peer list. MCP
|
|
25
|
+
server instructions plus a managed Codex `AGENTS.md` block teach Codex to call it at task
|
|
26
|
+
start, when scope changes, and on completion—no Codex hook approval required. Run inside a
|
|
27
|
+
project containing `brain.klypix`, or run `npx klypix-mcp link` in each brain project.
|
|
28
|
+
The installer removes the obsolete global KLYPIX `--vault "."` table that could resolve
|
|
29
|
+
outside the active project; every unrelated Codex setting and MCP server is preserved.
|
|
30
|
+
|
|
31
|
+
Optional native lifecycle capture for mechanical prompt/tool/file events:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx klypix-mcp install --codex-hooks
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Codex owns this native layer's security decision and asks the user to review/trust the
|
|
38
|
+
hooks on surfaces that expose hook management. `brain_doctor` reports it separately as
|
|
39
|
+
off, execution-unverified, or active; the approval-free smart layer remains active either way.
|
|
25
40
|
|
|
26
41
|
Give a project a brain by dropping a `brain.klypix` in it — the
|
|
27
42
|
[KLYPIX app](https://klypix.com) does it in one click (*Save canvas as project brain*),
|
|
@@ -41,7 +56,7 @@ managed files without changing them with `npx klypix-mcp link --check`.
|
|
|
41
56
|
|
|
42
57
|
The difference from a folder of notes is not the shape — it's that this memory is a **mechanism, not a filing convention**:
|
|
43
58
|
|
|
44
|
-
- **It briefs
|
|
59
|
+
- **It briefs each task, not just each session.** `brain_sync` ranks a bounded memory capsule from the actual task intent and expected files. The full generated brief remains available for broad history/status work, while the always-loaded `AGENTS.md` fallback stays compact. Measured on our own brain: **73% of past decisions recovered with one search round (55% brief-only) vs 0% cold** (n=20).
|
|
45
60
|
- **It argues back.** `brain_challenge`: propose a decision and the brain answers with receipts — *"you reversed this on June 12; here's the correction — captured by a different agent, coordinate before overriding."* Deterministic evidence only (correction-cues, opposite-polarity), never mere topical similarity. A memory that can't disagree with you is flattery.
|
|
46
61
|
- **It knows when it's stale.** Decision cards can anchor to the exact code they were decided against (git blob OID). When that code moves on, the card raises its hand in the next brief. Their notes rot silently; ours confess.
|
|
47
62
|
- **It answers from the past.** `brain_ask` with `as_of: 2026-03-01` answers what the project believed *then* — corrections from the future never leak backwards.
|
|
@@ -59,7 +74,8 @@ And it's not a cage: everything **exports to Markdown and JSON Canvas** in one c
|
|
|
59
74
|
|
|
60
75
|
## Setup for plain MCP clients
|
|
61
76
|
|
|
62
|
-
Any MCP client gets the tools without
|
|
77
|
+
Any MCP client gets the tools and connection-lifecycle presence without host hooks. For
|
|
78
|
+
**Claude Desktop**, in `claude_desktop_config.json`:
|
|
63
79
|
|
|
64
80
|
```json
|
|
65
81
|
{
|
|
@@ -72,9 +88,14 @@ Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `cla
|
|
|
72
88
|
}
|
|
73
89
|
```
|
|
74
90
|
|
|
75
|
-
|
|
91
|
+
If the vault contains `brain.klypix`, that MCP connection also appears as an active
|
|
92
|
+
session. `brain_sync` adds compact task memory, expected-file coordination, task-only peer
|
|
93
|
+
reporting, structured exact-overlap warnings, automatic conflict alerts, timing receipts,
|
|
94
|
+
and one-time peer-note delivery. Then ask your
|
|
95
|
+
agent things like *"what did we decide about auth?"*, *"challenge this: let's switch
|
|
96
|
+
to polling"*, or *"turn these notes into a board."*
|
|
76
97
|
|
|
77
|
-
## The
|
|
98
|
+
## The 18 verbs
|
|
78
99
|
|
|
79
100
|
| Tool | What it does |
|
|
80
101
|
|---|---|
|
|
@@ -83,9 +104,11 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
|
|
|
83
104
|
| `brain_note` | Capture with the full lifecycle — supersede / ✓ resolve / ~ update / 🛠 skill / closes: |
|
|
84
105
|
| `brain_reconcile` | Find stale-vs-correction pairs + unrecorded migrations |
|
|
85
106
|
| `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
|
|
107
|
+
| `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery, and unresolved views |
|
|
86
108
|
| `brain_garden` | Maintenance pass over the brain |
|
|
87
|
-
| `brain_doctor` | Self-diagnosis: version, host adapters, active
|
|
109
|
+
| `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, projection drift |
|
|
88
110
|
| `brain_message` | Session-to-session coordination notes |
|
|
111
|
+
| `brain_sync` | Context Gateway: compact task memory, active-task peers, structured conflicts, automatic alerts, and timing |
|
|
89
112
|
| `brain_connect` | Find + draw related-but-unlinked cards |
|
|
90
113
|
| `canvas_view` | The board as an MCP App — Apps-capable chats get an interactive spatial view; everyone else gets clean text |
|
|
91
114
|
| `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
|
|
@@ -97,23 +120,25 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
|
|
|
97
120
|
|
|
98
121
|
### Tools vs. the *automatic* brain
|
|
99
122
|
|
|
100
|
-
This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*),
|
|
123
|
+
This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*), automatically registers presence for the lifetime of its authorized connection, and receives the Context Gateway workflow through standard MCP server instructions. `install` wires Claude Code's existing capture path and installs the local runtime; `link` projects repository-level MCP and instruction files for Codex and other coding agents. Codex and other clients retrieve relevant project memory and coordinate task intent/files through one `brain_sync` call, while `brain_note` captures durable decisions explicitly; optional host hooks add mechanically guaranteed lifecycle capture.
|
|
101
124
|
|
|
102
125
|
### Agent-neutral live presence
|
|
103
126
|
|
|
104
|
-
An active session means
|
|
105
|
-
recent-chat list is history, not presence.
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
An active session means an authorized MCP connection or host lifecycle adapter
|
|
128
|
+
heartbeated within 10 minutes; a row in a recent-chat list is history, not presence.
|
|
129
|
+
Each MCP connection registers at initialization and removes itself on disconnect.
|
|
130
|
+
Optional host adapters merge into that same logical session rather than double-counting
|
|
131
|
+
it, enrich it with intent/files, and remove only their own channel. The TTL covers crashes.
|
|
108
132
|
|
|
109
|
-
Future hosts
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
133
|
+
Future hosts get baseline support merely by connecting the MCP server. A deeper adapter
|
|
134
|
+
can import `klypix-mcp/presence` and map lifecycle events to `upsertSession`,
|
|
135
|
+
`removeSession`, and `receiveMessages`. The shared contract accepts `id`, `client`,
|
|
136
|
+
`surface`, `model`, `branch`, `intent`, touched `files`, and adapter `channel`;
|
|
137
|
+
host-specific transcript parsing stays outside the protocol.
|
|
113
138
|
|
|
114
139
|
### Updates — the propagation contract
|
|
115
140
|
|
|
116
|
-
`install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain
|
|
141
|
+
`install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain` and keeps the current brain project's Codex config machine-portable. Updates are automatic through the Claude session-start hook: it checks npm (≤ once/24h, fail-open, disable with `KLYPIX_AUTO_UPDATE=0`) and self-installs newer releases. One honest caveat: a running stdio server can't hot-swap — a new binary loads on the next full app launch (or `/mcp` reconnect); `brain_doctor`'s RUNNING line tells you when that's needed.
|
|
117
142
|
|
|
118
143
|
## Also speaks A2A (Agent-to-Agent)
|
|
119
144
|
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// npx klypix-mcp install # install / update the brain on this machine
|
|
11
11
|
// npx klypix-mcp install --force # overwrite even a newer / dev-deployed brain
|
|
12
|
+
// npx klypix-mcp install --codex-hooks # optional prompt/file awareness; Codex asks for trust
|
|
12
13
|
//
|
|
13
14
|
// Never-throws-silently: it's an explicit CLI, so it reports what it did and exits
|
|
14
15
|
// non-zero on a real failure. Never wires a broken settings.json (refuse + restore).
|
|
@@ -18,10 +19,14 @@ import path from 'path';
|
|
|
18
19
|
import { fileURLToPath } from 'url';
|
|
19
20
|
import {
|
|
20
21
|
connectCodexMcpServer,
|
|
22
|
+
disconnectCodexMcpServer,
|
|
21
23
|
mcpServerEntry,
|
|
22
24
|
mergeCodexGlobalInstructions,
|
|
23
25
|
} from '../src/agent-rules.mjs';
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
codexPresenceHookStatus,
|
|
28
|
+
mergeCodexPresenceHooks,
|
|
29
|
+
} from '../src/codex-hooks.mjs';
|
|
25
30
|
|
|
26
31
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
32
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
@@ -29,6 +34,7 @@ const SRC = path.join(PKG_ROOT, 'src');
|
|
|
29
34
|
const BIN = path.join(PKG_ROOT, 'bin');
|
|
30
35
|
const VERSION = (() => { try { return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version || ''; } catch { return ''; } })();
|
|
31
36
|
const FORCE = process.argv.includes('--force');
|
|
37
|
+
const CODEX_HOOKS = process.argv.includes('--codex-hooks');
|
|
32
38
|
|
|
33
39
|
const HOME = os.homedir();
|
|
34
40
|
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
@@ -38,44 +44,96 @@ const CODEX_CONFIG = path.join(HOME, '.codex', 'config.toml');
|
|
|
38
44
|
const HOOK_MARK = 'global-brain-hook';
|
|
39
45
|
const exists = (p) => { try { fs.statSync(p); return true; } catch { return false; } };
|
|
40
46
|
const fwd = (p) => p.replace(/\\/g, '/');
|
|
47
|
+
const copyRetrySleepSync = (ms) => {
|
|
48
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
|
|
49
|
+
catch { /* best effort */ }
|
|
50
|
+
};
|
|
51
|
+
function copyFileRobust(src, dest, tries = 8) {
|
|
52
|
+
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
53
|
+
try { fs.copyFileSync(src, dest); return; }
|
|
54
|
+
catch (error) {
|
|
55
|
+
const retryable = ['EBUSY', 'EPERM', 'EACCES'].includes(error?.code);
|
|
56
|
+
if (!retryable || attempt === tries) throw error;
|
|
57
|
+
copyRetrySleepSync(20 * attempt);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
41
61
|
function copyDir(src, dest) {
|
|
42
62
|
fs.mkdirSync(dest, { recursive: true });
|
|
43
63
|
for (const e of fs.readdirSync(src, { withFileTypes: true })) {
|
|
44
64
|
const s = path.join(src, e.name), d = path.join(dest, e.name);
|
|
45
|
-
if (e.isDirectory()) copyDir(s, d); else if (e.isFile())
|
|
65
|
+
if (e.isDirectory()) copyDir(s, d); else if (e.isFile()) copyFileRobust(s, d);
|
|
46
66
|
}
|
|
47
67
|
}
|
|
48
68
|
|
|
49
|
-
// Codex has
|
|
50
|
-
//
|
|
51
|
-
//
|
|
69
|
+
// Codex has two approval-free layers and one optional native layer:
|
|
70
|
+
// project-scoped MCP gives it tools + live presence + brain_sync, while the
|
|
71
|
+
// conditional ~/.codex/AGENTS.md block makes the Context Gateway workflow
|
|
72
|
+
// automatic on every task. Native lifecycle hooks add mechanical prompt/file
|
|
73
|
+
// capture only when the user explicitly asks for --codex-hooks and approves them
|
|
74
|
+
// in a Codex surface that exposes trust review.
|
|
52
75
|
function wireCodex() {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
76
|
+
const projectDir = process.cwd();
|
|
77
|
+
const hasProjectBrain = exists(path.join(projectDir, 'brain.klypix'))
|
|
78
|
+
|| exists(path.join(projectDir, 'brain.any'));
|
|
79
|
+
const projectConfig = path.join(projectDir, '.codex', 'config.toml');
|
|
80
|
+
const mcp = hasProjectBrain
|
|
81
|
+
? connectCodexMcpServer({
|
|
82
|
+
configPath: projectConfig,
|
|
83
|
+
// Project config is commonly committed. Keep it portable: the
|
|
84
|
+
// package invocation that is running this installer is exactly the
|
|
85
|
+
// version the project asked for, while an absolute home path would
|
|
86
|
+
// dirty the repo and break it on every other machine.
|
|
87
|
+
entry: { command: 'npx', args: ['-y', 'klypix-mcp', '--vault', '.'] },
|
|
88
|
+
cwd: '..',
|
|
89
|
+
})
|
|
90
|
+
: { ok: true, action: 'skipped-no-project-brain', path: projectConfig };
|
|
91
|
+
// Pre-1.35 installed a global `--vault "."` entry. A global Codex process
|
|
92
|
+
// resolves that dot from the app install directory, not the user's project,
|
|
93
|
+
// so it can silently bind the wrong brain and override the correct project
|
|
94
|
+
// table. Remove only KLYPIX-owned global tables; preserve every other server.
|
|
95
|
+
const globalMcp = disconnectCodexMcpServer({ configPath: CODEX_CONFIG });
|
|
57
96
|
const instructions = mergeCodexGlobalInstructions(HOME);
|
|
58
97
|
const hookScript = path.join(BRAIN_DIR, 'codex-brain-hook.mjs');
|
|
59
|
-
const presence = exists(hookScript)
|
|
98
|
+
const presence = CODEX_HOOKS && exists(hookScript)
|
|
60
99
|
? mergeCodexPresenceHooks({
|
|
61
100
|
home: HOME,
|
|
62
101
|
command: `node "${fwd(hookScript)}"`,
|
|
63
102
|
})
|
|
64
|
-
: {
|
|
65
|
-
|
|
103
|
+
: {
|
|
104
|
+
ok: true,
|
|
105
|
+
action: codexPresenceHookStatus(HOME).installed ? 'existing' : 'off',
|
|
106
|
+
...codexPresenceHookStatus(HOME),
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
mcp,
|
|
110
|
+
globalMcp,
|
|
111
|
+
instructions,
|
|
112
|
+
presence,
|
|
113
|
+
ok: mcp.ok && globalMcp.ok && instructions.ok && presence.ok,
|
|
114
|
+
};
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
function reportCodex(result) {
|
|
69
118
|
if (result.ok) {
|
|
70
|
-
const
|
|
71
|
-
? '
|
|
72
|
-
: `
|
|
73
|
-
|
|
119
|
+
const mcp = result.mcp.action === 'skipped-no-project-brain'
|
|
120
|
+
? 'project MCP skipped (run `npx klypix-mcp link` inside a brain project)'
|
|
121
|
+
: `project MCP + automatic presence/Context Gateway (${result.mcp.action})`;
|
|
122
|
+
const enhanced = result.presence.action === 'off'
|
|
123
|
+
? 'enhanced hooks off (optional)'
|
|
124
|
+
: result.presence.action === 'existing'
|
|
125
|
+
? `enhanced hooks already configured (${result.presence.executionStatus || 'unverified'})`
|
|
126
|
+
: `enhanced hooks ${result.presence.action} — approve/review them in Codex /hooks`;
|
|
127
|
+
console.log(`✓ wired Codex: ${mcp} + approval-free task guidance (${result.instructions.action}) + ${enhanced}`);
|
|
128
|
+
if (result.globalMcp.action === 'disconnected') {
|
|
129
|
+
console.log('✓ removed obsolete global Codex KLYPIX MCP entry (it could resolve "." outside the project); other MCP servers were preserved');
|
|
130
|
+
}
|
|
74
131
|
return;
|
|
75
132
|
}
|
|
76
133
|
if (!result.mcp.ok) console.error(`⚠ Codex MCP was not changed: ${result.mcp.error}`);
|
|
134
|
+
if (!result.globalMcp.ok) console.error(`⚠ obsolete global Codex MCP entry was not removed: ${result.globalMcp.error}`);
|
|
77
135
|
if (!result.instructions.ok) console.error(`⚠ Codex guidance was not changed: ${result.instructions.error}`);
|
|
78
|
-
if (!result.presence.ok) console.error(`⚠ Codex
|
|
136
|
+
if (!result.presence.ok) console.error(`⚠ Codex enhanced hooks were not changed: ${result.presence.error}`);
|
|
79
137
|
console.error(' Claude Code installation is intact. Fix the Codex warning, then re-run this command.');
|
|
80
138
|
}
|
|
81
139
|
|
|
@@ -134,6 +192,10 @@ function migrateProjectMcpConfig() {
|
|
|
134
192
|
const entry = servers && servers['klypix-canvas'];
|
|
135
193
|
if (!entry || (entry.command !== 'npx' && entry.command !== 'node')) return null; // absent / hand-customized → leave it
|
|
136
194
|
const args = Array.isArray(entry.args) ? entry.args : [];
|
|
195
|
+
// A repo-relative node launch (for example scripts/klypix-mcp-server.mjs)
|
|
196
|
+
// is deliberately portable and often tracks a project-owned bundle.
|
|
197
|
+
// Never replace it with a machine-specific ~/.claude path.
|
|
198
|
+
if (entry.command === 'node' && args[0] && !path.isAbsolute(String(args[0]))) return null;
|
|
137
199
|
const vi = args.indexOf('--vault');
|
|
138
200
|
const vault = vi >= 0 && args[vi + 1] ? args[vi + 1] : '.';
|
|
139
201
|
const next = mcpServerEntry({ vault }); // local now that the bundle is installed
|
|
@@ -154,7 +216,7 @@ const flatten = (code) => code
|
|
|
154
216
|
.replace(/\.\.\/src\/klypix-(core|format)\.mjs/g, './klypix-$1.mjs')
|
|
155
217
|
// brain-doctor + agent-rules (the server's lazy `import('../src/brain-doctor.mjs')`
|
|
156
218
|
// for the brain_doctor tool) → flat sibling refs in the runtime layout.
|
|
157
|
-
.replace(/\.\.\/src\/(brain-doctor|agent-rules)\.mjs/g, './$1.mjs')
|
|
219
|
+
.replace(/\.\.\/src\/(brain-doctor|agent-rules|mcp-presence)\.mjs/g, './$1.mjs')
|
|
158
220
|
.replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
|
|
159
221
|
|
|
160
222
|
const gotLock = acquireLock();
|
|
@@ -211,7 +273,7 @@ try {
|
|
|
211
273
|
// canvas-view-app.html is the canvas_view MCP App UI — staged raw (an HTML
|
|
212
274
|
// file must never get a JS-comment banner) beside the flat server, which
|
|
213
275
|
// resolves it via its ./canvas-view-app.html candidate path.
|
|
214
|
-
for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'agent-presence.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
276
|
+
for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
215
277
|
const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
|
|
216
278
|
}
|
|
217
279
|
for (const [src, dst] of [['klypix-mcp.mjs', 'klypix-mcp-server.mjs'], ['klypix-a2a.mjs', 'klypix-a2a-server.mjs']]) {
|
|
@@ -282,7 +344,8 @@ try {
|
|
|
282
344
|
else console.error(`⚠ readiness: ${notWired.length} hook(s) did NOT take (${notWired.join(', ')}) — the brain will read but not capture/sync. Re-run \`npx klypix-mcp install --force\` or check ${SETTINGS}.`);
|
|
283
345
|
console.log(`✓ MCP server runs from the local bundle (node ${fwd(path.join(BRAIN_DIR, 'klypix-mcp-server.mjs'))}) — no npx cache, works offline, always the installed version.`);
|
|
284
346
|
if (migrated) console.log(`✓ migrated ${migrated.file} klypix-canvas server: ${migrated.from} → ${migrated.to} (backup: .mcp.json.klypix-bak). Reconnect (/mcp) or restart to pick it up.`);
|
|
285
|
-
console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex
|
|
347
|
+
console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex gets the brain_sync Context Gateway (compact task memory + clean peers + automatic conflict alerts) with no hook trust prompt.');
|
|
348
|
+
console.log(' Optional mechanical Codex lifecycle capture: re-run with `--codex-hooks`, then approve/review KLYPIX in a Codex surface that supports hook trust.');
|
|
286
349
|
console.log(' ⚠ Open sessions keep their OLD server until relaunched: fully quit & reopen the app (a session resume / new chat does NOT respawn the MCP server). `brain_doctor`\'s RUNNING line confirms when you\'re current.');
|
|
287
350
|
console.log(' Verify anytime: `npx klypix-mcp doctor` (is the brain current + wired + in sync, who else is live).');
|
|
288
351
|
} catch (e) {
|
package/bin/klypix-link.mjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Synchronous top-level by design: the dispatcher does `await import(this); process.exit(0)`,
|
|
18
18
|
// so all work (and its logs) must complete during module evaluation, before exit.
|
|
19
19
|
import path from 'path';
|
|
20
|
-
import { linkProject } from '../src/agent-rules.mjs';
|
|
20
|
+
import { compactAgentsBrief, linkProject } from '../src/agent-rules.mjs';
|
|
21
21
|
|
|
22
22
|
try {
|
|
23
23
|
const args = process.argv.slice(3);
|
|
@@ -25,6 +25,7 @@ try {
|
|
|
25
25
|
const dirArg = args.find(a => !a.startsWith('-'));
|
|
26
26
|
const projectDir = path.resolve(dirArg || process.cwd());
|
|
27
27
|
const { rules, mcp, hasBrain, version } = linkProject(projectDir, { check });
|
|
28
|
+
const compactBrief = await compactAgentsBrief(projectDir, { check });
|
|
28
29
|
|
|
29
30
|
if (check) {
|
|
30
31
|
// Audit-only: classify, report, exit 1 on any drift.
|
|
@@ -34,7 +35,11 @@ try {
|
|
|
34
35
|
for (const r of mcp) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.why ? ' (' + r.why + ')' : ''}`);
|
|
35
36
|
console.log('\n Rules / instructions:');
|
|
36
37
|
for (const r of rules) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.stampedVersion ? ' (stamped v' + r.stampedVersion + ')' : ''}`);
|
|
37
|
-
|
|
38
|
+
if (compactBrief.status !== 'skipped') {
|
|
39
|
+
console.log(` ${mark(compactBrief.status)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md — ${compactBrief.status.toUpperCase()}${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
|
|
40
|
+
}
|
|
41
|
+
const briefDrift = ['stale', 'error'].includes(compactBrief.status) ? [compactBrief] : [];
|
|
42
|
+
const drift = [...rules, ...mcp, ...briefDrift].filter(r => r.status !== 'ok' && r.status !== 'skipped');
|
|
38
43
|
if (!drift.length) { console.log(`\n✓ in sync — all ${rules.length + mcp.length} managed file(s) match brain v${version}.`); process.exit(0); }
|
|
39
44
|
console.log(`\n✗ ${drift.length} file(s) drifted (stale / hand-edited / missing) — run \`npx klypix-mcp link\` to re-project.`);
|
|
40
45
|
process.exit(1);
|
|
@@ -46,8 +51,11 @@ try {
|
|
|
46
51
|
for (const r of mcp) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
|
|
47
52
|
console.log('\n Rules (so each tool auto-reads + captures the brain):');
|
|
48
53
|
for (const r of rules) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
|
|
54
|
+
if (compactBrief.status !== 'skipped') {
|
|
55
|
+
console.log(` ${mark(compactBrief.action)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
|
|
56
|
+
}
|
|
49
57
|
|
|
50
|
-
const changed = [...rules, ...mcp].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
|
|
58
|
+
const changed = [...rules, ...mcp, compactBrief].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
|
|
51
59
|
console.log(`\n✓ ${changed} file(s) written/updated — every agent opened in this project now reads + captures ./brain.klypix.`);
|
|
52
60
|
console.log(' Codex gets .codex/config.toml + AGENTS.md; Cline & Windsurf use their global MCP config plus project rules.');
|
|
53
61
|
console.log(' Verify anytime with `npx klypix-mcp link --check` (or `npx klypix-mcp doctor`).');
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -27,8 +27,10 @@ import {
|
|
|
27
27
|
resolveVault, getEmbedder, buildKlypixMap, cardSchema, connSchema,
|
|
28
28
|
opListCanvases, opReadCanvas, opSearchCanvases, opSearchAllBrains,
|
|
29
29
|
opBrainInsights, opBrainConnect, opBrainReconcile, opBrainGarden, opCreateCanvas, opAddToCanvas, opBrainNote, opBrainMessage, opBrainAsk, opBrainChallenge, opCanvasView, opBrainLens,
|
|
30
|
+
opBrainTaskContext,
|
|
30
31
|
} from '../src/klypix-core.mjs';
|
|
31
32
|
import { mcpServerEntry } from '../src/agent-rules.mjs';
|
|
33
|
+
import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS } from '../src/mcp-presence.mjs';
|
|
32
34
|
|
|
33
35
|
// Real package version for the MCP handshake (was hardcoded '1.0.0', which
|
|
34
36
|
// misled every client/version diagnosis — it could never reflect the true release).
|
|
@@ -102,17 +104,21 @@ if (process.argv[2] === 'init') {
|
|
|
102
104
|
|
|
103
105
|
const vaultArgIdx = process.argv.indexOf('--vault');
|
|
104
106
|
const VAULT = resolveVault(vaultArgIdx >= 0 ? process.argv[vaultArgIdx + 1] : undefined);
|
|
107
|
+
const server = new McpServer(
|
|
108
|
+
{ name: 'klypix-canvas', version: PKG_VERSION },
|
|
109
|
+
{ instructions: KLYPIX_MCP_INSTRUCTIONS },
|
|
110
|
+
);
|
|
111
|
+
const mcpPresence = createMcpPresence({ server, initialVault: VAULT });
|
|
105
112
|
|
|
106
113
|
// Map a protocol-neutral core result → an MCP tool result.
|
|
107
114
|
const toContent = (r) => {
|
|
108
115
|
const content = r.blocks.map(b => b.kind === 'image'
|
|
109
116
|
? { type: 'image', data: b.data, mimeType: b.mime }
|
|
110
117
|
: { type: 'text', text: b.text });
|
|
111
|
-
|
|
118
|
+
const result = r.isError ? { content, isError: true } : { content };
|
|
119
|
+
return mcpPresence.decorateToolResult(result);
|
|
112
120
|
};
|
|
113
121
|
|
|
114
|
-
const server = new McpServer({ name: 'klypix-canvas', version: PKG_VERSION });
|
|
115
|
-
|
|
116
122
|
server.registerTool('list_canvases', {
|
|
117
123
|
title: 'List KLYPIX canvases',
|
|
118
124
|
description: 'List all .klypix / .any canvas files in the vault, with card and connection counts.',
|
|
@@ -265,7 +271,7 @@ server.registerTool('brain_note', {
|
|
|
265
271
|
|
|
266
272
|
server.registerTool('brain_message', {
|
|
267
273
|
title: 'Message the other live agent sessions on this project (one-time note, not a brain card)',
|
|
268
|
-
description: 'Leave a DELIBERATE, targeted note for the OTHER active agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can
|
|
274
|
+
description: 'Leave a DELIBERATE, targeted note for the OTHER active agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can send and receive through the shared presence lane. Hookless MCP peers receive each note once on their next KLYPIX tool call (and may also see a host logging notification); lifecycle hooks provide more proactive delivery. Ephemeral (expires in 24h) and NOT persisted to the brain — for a durable decision use brain_note instead.',
|
|
269
275
|
inputSchema: {
|
|
270
276
|
text: z.string().describe('The note to deliver (kept to 400 chars).'),
|
|
271
277
|
to: z.string().optional().describe('Target hint — a peer session id-prefix or branch name; omit or "all" for every live session.'),
|
|
@@ -273,12 +279,75 @@ server.registerTool('brain_message', {
|
|
|
273
279
|
},
|
|
274
280
|
}, async ({ text, to, canvas }) => {
|
|
275
281
|
let via; try { via = server.server.getClientVersion()?.name; } catch { /* optional */ }
|
|
282
|
+
mcpPresence.noteSent(text);
|
|
276
283
|
return toContent(await opBrainMessage({ vault: VAULT, canvas, text, to, via }));
|
|
277
284
|
});
|
|
278
285
|
|
|
286
|
+
server.registerTool('brain_sync', {
|
|
287
|
+
title: 'KLYPIX Context Gateway — synchronize task, peers, conflicts, and relevant memory',
|
|
288
|
+
description: 'APPROVAL-FREE task gateway over the authorized MCP connection. Call FIRST with a concise intent and expected files, again when scope changes, and with phase:"complete" before the final response. One bounded response returns compact task-relevant brain context, active TASK peers (idle connections hidden), one-time messages, structured exact-file conflicts, and automatic late-arrival overlap alerts. Portable across Codex/Antigravity and every MCP host; native hooks are optional.',
|
|
289
|
+
annotations: {
|
|
290
|
+
destructiveHint: false,
|
|
291
|
+
idempotentHint: true,
|
|
292
|
+
openWorldHint: false,
|
|
293
|
+
},
|
|
294
|
+
inputSchema: {
|
|
295
|
+
intent: z.string().max(160).optional().describe('One sentence describing the current task. Supply for start/checkpoint; completion clears it.'),
|
|
296
|
+
files: z.array(z.string()).max(20).optional().describe('Project-relative files you expect to touch or have touched. Exact overlaps with peers are flagged.'),
|
|
297
|
+
phase: z.enum(['start', 'checkpoint', 'complete']).optional().describe('start replaces prior task scope; checkpoint merges changed scope; complete clears task intent/files. Default: checkpoint.'),
|
|
298
|
+
include_context: z.boolean().optional().describe('Include fast task-relevant brain cards in the same response. Defaults true; ignored for phase complete.'),
|
|
299
|
+
},
|
|
300
|
+
}, async ({ intent, files, phase, include_context }) => {
|
|
301
|
+
const totalStartedAt = Date.now();
|
|
302
|
+
const report = mcpPresence.sync({ intent, files, phase });
|
|
303
|
+
let taskContext = null;
|
|
304
|
+
if (phase !== 'complete' && include_context !== false && (intent || files?.length)) {
|
|
305
|
+
taskContext = await opBrainTaskContext({
|
|
306
|
+
vault: VAULT,
|
|
307
|
+
intent,
|
|
308
|
+
files,
|
|
309
|
+
k: 5,
|
|
310
|
+
budgetChars: 2800,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const contextText = taskContext?.blocks
|
|
314
|
+
?.filter((block) => block.kind === 'text')
|
|
315
|
+
.map((block) => block.text)
|
|
316
|
+
.filter(Boolean)
|
|
317
|
+
.join('\n\n') || '';
|
|
318
|
+
const totalMs = Math.max(0, Date.now() - totalStartedAt);
|
|
319
|
+
const structuredContent = {
|
|
320
|
+
...(report.structured || {
|
|
321
|
+
schemaVersion: 1,
|
|
322
|
+
status: 'idle',
|
|
323
|
+
phase: phase || 'checkpoint',
|
|
324
|
+
}),
|
|
325
|
+
context: taskContext?.context || {
|
|
326
|
+
mode: 'not-requested',
|
|
327
|
+
hits: [],
|
|
328
|
+
sufficient: false,
|
|
329
|
+
durationMs: 0,
|
|
330
|
+
},
|
|
331
|
+
timingMs: {
|
|
332
|
+
...(report.structured?.timingMs || {}),
|
|
333
|
+
context: taskContext?.context?.durationMs || 0,
|
|
334
|
+
total: totalMs,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
const timingText = `Context Gateway total: ${totalMs}ms`
|
|
338
|
+
+ (taskContext?.context ? ` (memory ${taskContext.context.durationMs}ms)` : '') + '.';
|
|
339
|
+
return {
|
|
340
|
+
content: [{
|
|
341
|
+
type: 'text',
|
|
342
|
+
text: [report.text, contextText, timingText].filter(Boolean).join('\n\n'),
|
|
343
|
+
}],
|
|
344
|
+
structuredContent,
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
|
|
279
348
|
server.registerTool('brain_doctor', {
|
|
280
349
|
title: 'Brain doctor — is this brain current, wired, and in sync?',
|
|
281
|
-
description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 4-hook capture readiness), CODEX (
|
|
350
|
+
description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 4-hook capture readiness), CODEX (automatic MCP presence plus optional enhanced-hook status), TOOLS (discoverable MCP verbs), SESSIONS (all active presence-adapter sessions across hosts, never recent-chat history), and HARNESS (projection drift). Use to answer "is my brain current, correctly installed, in sync, and who is actually live?" without file-spelunking. Never writes. The MCP-callable twin of `npx klypix-mcp doctor`.',
|
|
282
351
|
inputSchema: {
|
|
283
352
|
project: z.string().optional().describe('Project dir to audit harness + peers for. Defaults to the server\'s working directory.'),
|
|
284
353
|
check_npm: z.boolean().optional().describe('Also fetch npm latest to flag a stale brain (default false — this one does a network `npm view`).'),
|
|
@@ -297,7 +366,7 @@ server.registerTool('brain_doctor', {
|
|
|
297
366
|
// makes the RUNNING check report the caller's actual server version, never a
|
|
298
367
|
// phantom another session's heartbeat wrote to the shared registry.
|
|
299
368
|
const report = inspect({ projectDir: project ? path.resolve(project) : process.cwd(), npmLatest, self: { pid: process.pid, version: PKG_VERSION } });
|
|
300
|
-
return { content: [{ type: 'text', text: render(report, { color: false }) }] };
|
|
369
|
+
return mcpPresence.decorateToolResult({ content: [{ type: 'text', text: render(report, { color: false }) }] });
|
|
301
370
|
} catch (e) {
|
|
302
371
|
return { content: [{ type: 'text', text: `brain_doctor unavailable: ${e?.message || e}` }], isError: true };
|
|
303
372
|
}
|
|
@@ -394,9 +463,14 @@ if (!canvasViewAsApp) {
|
|
|
394
463
|
}
|
|
395
464
|
|
|
396
465
|
const transport = new StdioServerTransport();
|
|
466
|
+
server.server.oninitialized = () => {
|
|
467
|
+
mcpPresence.start(VAULT);
|
|
468
|
+
recordRunningServer();
|
|
469
|
+
log(`ready · vault=${VAULT} · presence=mcp`);
|
|
470
|
+
};
|
|
471
|
+
server.server.onclose = () => mcpPresence.stop();
|
|
472
|
+
process.once('exit', () => mcpPresence.stop());
|
|
397
473
|
await server.connect(transport);
|
|
398
|
-
recordRunningServer();
|
|
399
|
-
log(`ready · vault=${VAULT}`);
|
|
400
474
|
// Pre-warm the on-device embedder in the BACKGROUND so the first cross-project
|
|
401
475
|
// search of a session is already semantic, not a lexical fallback.
|
|
402
476
|
getEmbedder(log).then(p => log(p ? 'semantic ready (pre-warmed)' : 'semantic unavailable — lexical only')).catch(() => {});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "Every project gets a brain — one open .klypix file your AI agents read, write, and argue from, over MCP. Works with Claude, Codex, Cursor, Cline, any model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
".": "./index.mjs",
|
|
40
40
|
"./format": "./src/klypix-format.mjs",
|
|
41
41
|
"./core": "./src/klypix-core.mjs",
|
|
42
|
-
"./presence": "./src/agent-presence.mjs"
|
|
42
|
+
"./presence": "./src/agent-presence.mjs",
|
|
43
|
+
"./mcp-presence": "./src/mcp-presence.mjs"
|
|
43
44
|
},
|
|
44
45
|
"files": [
|
|
45
46
|
"src",
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"node": ">=18"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
|
-
"test": "node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs"
|
|
58
|
+
"test": "node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/context-gateway.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs"
|
|
58
59
|
},
|
|
59
60
|
"dependencies": {
|
|
60
61
|
"@modelcontextprotocol/ext-apps": "^1.7.4",
|