mindgraph-mcp 0.14.7 → 0.14.8

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/mindgraph-mcp)](https://www.npmjs.com/package/mindgraph-mcp)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
5
 
6
- Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, and any MCP-compatible client.
6
+ Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, Codex, and any MCP-compatible client.
7
7
 
8
8
  ## Install
9
9
 
@@ -77,7 +77,10 @@ which state you're in).
77
77
 
78
78
  The hooks open/rebind a Session at start and inject the current work brief,
79
79
  stamp every MindGraph tool call with verified session/repo/commit provenance,
80
- and prompt one reflection checkpoint before a substantial session ends. They
80
+ and prompt one reflection checkpoint before a substantial session ends. A
81
+ session automatically re-claims only *this* agent's own prior work; other
82
+ tasks appear in the brief as context, and the agent claims one deliberately
83
+ when it starts that work. They
81
84
  **fail open** — if MindGraph is unreachable, your coding session is never
82
85
  blocked. `--scope project` (with `--project-dir`) installs into a single
83
86
  repository's `.claude/settings.json` instead; `uninstall-hooks` reverses either
@@ -87,18 +90,36 @@ project settings.
87
90
 
88
91
  ### Coding agents (Codex)
89
92
 
90
- The server is harness-neutral — Codex gets the full coding tool surface today:
93
+ Codex gets the same durable work substrate through its native command hooks.
94
+ Register the MCP server with a distinct agent identity, then install the hooks:
91
95
 
92
96
  ```bash
93
97
  codex mcp add mindgraph --env MINDGRAPH_API_KEY=mg_your_key_here \
94
98
  --env MINDGRAPH_PROFILE=coding --env MINDGRAPH_HARNESS=codex \
99
+ --env MINDGRAPH_AGENT_ID=codex:your-name \
95
100
  -- npx -y mindgraph-mcp@latest
101
+
102
+ npx -y mindgraph-mcp install-hooks --harness codex --scope user \
103
+ --api-key mg_your_key_here --agent-id codex:your-name
96
104
  ```
97
105
 
98
106
  (or the equivalent `[mcp_servers.mindgraph]` block in Codex's `config.toml`).
99
- The hooks adapter for Codex — automatic session lifecycle and brief injection —
100
- is planned; until then, ask the agent to call `mindgraph_plan resume_work` at
101
- the start of a session.
107
+ The installer copies the pinned self-contained runner to
108
+ `~/.mindgraph/bin/mindgraph-hook.cjs`, merge-safely upserts only entries marked
109
+ `--owner mindgraph`, and writes user hooks to `$CODEX_HOME/hooks.json`
110
+ (`~/.codex/hooks.json` by default). Use `--scope project --project-dir …` for
111
+ `<project>/.codex/hooks.json`. Re-run the install command to refresh owned
112
+ entries; foreign hooks are never changed. Codex requires newly installed or
113
+ changed command hooks to be reviewed with `/hooks` before they run.
114
+
115
+ At SessionStart the adapter opens/rebinds the graph Session and injects one
116
+ bounded work brief, re-claiming only this agent's own prior work.
117
+ PreToolUse replaces forged/absent invocation provenance
118
+ while preserving model-selected work targets, PostToolUse updates only the
119
+ disposable runtime ledger, Stop runs the once-per-session reflection
120
+ checkpoint, and SessionEnd performs best-effort cleanup. All paths fail open:
121
+ missing credentials or an unreachable MindGraph return Codex's no-op response
122
+ and never block the coding session.
102
123
 
103
124
  ## What You Get
104
125
 
@@ -146,7 +167,7 @@ mindgraph-mcp install Install into Claude Desktop config
146
167
  mindgraph-mcp install-code Install into Claude Code (--hooks: also install session hooks)
147
168
  mindgraph-mcp uninstall Remove from Claude Desktop config
148
169
  mindgraph-mcp uninstall-code Remove from Claude Code
149
- mindgraph-mcp install-hooks Install Claude Code hooks (--harness claude-code)
170
+ mindgraph-mcp install-hooks Install coding hooks (--harness claude-code|codex)
150
171
  mindgraph-mcp uninstall-hooks Remove MindGraph-owned hook entries
151
172
  mindgraph-mcp status Show installation status
152
173
  ```
package/dist/cli.js CHANGED
@@ -4917,7 +4917,7 @@ Capture knowledge when the user shares something worth remembering:
4917
4917
  server = new import_server.Server(
4918
4918
  {
4919
4919
  name: "mindgraph",
4920
- version: "0.14.7"
4920
+ version: "0.14.8"
4921
4921
  },
4922
4922
  {
4923
4923
  capabilities: {
@@ -5182,7 +5182,7 @@ function saveHookEnv(env, dir) {
5182
5182
  // src/cli-args.ts
5183
5183
  function parseArgs(argv) {
5184
5184
  const args = argv.slice(2);
5185
- let command = "serve";
5185
+ let command2 = "serve";
5186
5186
  let apiKey = process.env.MINDGRAPH_API_KEY;
5187
5187
  let baseUrl = process.env.MINDGRAPH_BASE_URL;
5188
5188
  let scope = "project";
@@ -5224,16 +5224,16 @@ function parseArgs(argv) {
5224
5224
  break;
5225
5225
  case "--help":
5226
5226
  case "-h":
5227
- command = "help";
5227
+ command2 = "help";
5228
5228
  break;
5229
5229
  default:
5230
5230
  if (!arg.startsWith("-")) {
5231
- command = arg;
5231
+ command2 = arg;
5232
5232
  }
5233
5233
  break;
5234
5234
  }
5235
5235
  }
5236
- return { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5236
+ return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5237
5237
  }
5238
5238
  function mcpAddFailureOutput(e) {
5239
5239
  const err5 = e;
@@ -5247,16 +5247,17 @@ function classifyMcpAddFailure(e) {
5247
5247
  return "other";
5248
5248
  }
5249
5249
 
5250
- // src/claude-hooks.ts
5250
+ // src/hook-core.ts
5251
5251
  var import_node_crypto2 = __toESM(require("crypto"));
5252
5252
  var import_node_fs3 = __toESM(require("fs"));
5253
5253
  var import_node_os3 = __toESM(require("os"));
5254
5254
  var import_node_path3 = __toESM(require("path"));
5255
5255
  var import_node_child_process2 = require("child_process");
5256
5256
  init_codegraph();
5257
- function defaultLedger(sessionId) {
5257
+ var NOOP = { kind: "noop" };
5258
+ function defaultLedger(sessionId, options) {
5258
5259
  return {
5259
- harness: "claude-code",
5260
+ harness: options.harness,
5260
5261
  harnessSessionId: sessionId,
5261
5262
  materialToolCalls: 0,
5262
5263
  mutatingOperations: 0,
@@ -5273,7 +5274,8 @@ function runtimeDir(options) {
5273
5274
  return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
5274
5275
  }
5275
5276
  function ledgerPath(sessionId, options) {
5276
- const key = import_node_crypto2.default.createHash("sha256").update(`claude-code\0${sessionId}`).digest("hex");
5277
+ const harness = "harness" in options ? options.harness : "claude-code";
5278
+ const key = import_node_crypto2.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
5277
5279
  return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
5278
5280
  }
5279
5281
  function waitBriefly(ms) {
@@ -5302,10 +5304,10 @@ function withLedger(sessionId, options, update) {
5302
5304
  }
5303
5305
  }
5304
5306
  if (lockFd === void 0) {
5305
- return update(defaultLedger(sessionId));
5307
+ return update(defaultLedger(sessionId, options));
5306
5308
  }
5307
5309
  try {
5308
- let ledger = defaultLedger(sessionId);
5310
+ let ledger = defaultLedger(sessionId, options);
5309
5311
  try {
5310
5312
  ledger = {
5311
5313
  ...ledger,
@@ -5356,8 +5358,9 @@ async function invocationContext(input, options) {
5356
5358
  workspaceFile: options.workspaceFile
5357
5359
  }).resolveRepository();
5358
5360
  return {
5359
- harness: "claude-code",
5361
+ harness: options.harness,
5360
5362
  harnessSessionId: input.session_id,
5363
+ harnessTurnId: input.turn_id,
5361
5364
  cwd: input.cwd,
5362
5365
  repoId: repository.repoId,
5363
5366
  branch: gitValue(input.cwd, ["branch", "--show-current"]),
@@ -5434,13 +5437,21 @@ async function repositoryScopeUids(input, client2, options, agentId) {
5434
5437
  )
5435
5438
  ];
5436
5439
  }
5440
+ function isOwnPriorWork(brief, agentId) {
5441
+ const reason = string(brief?.selection_reason);
5442
+ if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
5443
+ return true;
5444
+ }
5445
+ return string(object(brief?.lease)?.lease_owner_agent_id) === agentId;
5446
+ }
5437
5447
  async function sessionStart(input, client2, options) {
5438
- const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || "claude-code";
5448
+ const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || options.harness;
5449
+ const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
5439
5450
  const opened = object(
5440
5451
  await client2.session({
5441
5452
  action: "open",
5442
- label: `Claude Code ${input.session_id}`,
5443
- harness: "claude-code",
5453
+ label: `${harnessLabel} ${input.session_id}`,
5454
+ harness: options.harness,
5444
5455
  harness_session_id: input.session_id,
5445
5456
  model: input.model,
5446
5457
  agent_id: agentId
@@ -5458,7 +5469,7 @@ async function sessionStart(input, client2, options) {
5458
5469
  );
5459
5470
  const task = object(brief?.task);
5460
5471
  const lease = object(brief?.lease);
5461
- if (sessionUid && string(task?.uid) && number(task?.version)) {
5472
+ if (sessionUid && string(task?.uid) && number(task?.version) && isOwnPriorWork(brief, agentId)) {
5462
5473
  try {
5463
5474
  const claimed = object(
5464
5475
  await client2.plan({
@@ -5483,7 +5494,11 @@ async function sessionStart(input, client2, options) {
5483
5494
  } catch {
5484
5495
  }
5485
5496
  }
5486
- withLedger(input.session_id, options, (ledger) => {
5497
+ const briefText = boundedBrief(brief || { status: "no_eligible_work" });
5498
+ const briefHash = import_node_crypto2.default.createHash("sha256").update(briefText).digest("hex");
5499
+ const shouldInject = withLedger(input.session_id, options, (ledger) => {
5500
+ const changed = ledger.lastBriefHash !== briefHash;
5501
+ const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
5487
5502
  ledger.sessionUid = sessionUid;
5488
5503
  ledger.baselineWorktree = worktreeState(input.cwd);
5489
5504
  const selectedTask = object(brief?.task);
@@ -5495,14 +5510,10 @@ async function sessionStart(input, client2, options) {
5495
5510
  ledger.leaseEpoch = number(selectedLease?.lease_epoch);
5496
5511
  ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
5497
5512
  ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
5498
- return void 0;
5513
+ ledger.lastBriefHash = briefHash;
5514
+ return changed || reinjectCompact;
5499
5515
  });
5500
- return {
5501
- hookSpecificOutput: {
5502
- hookEventName: "SessionStart",
5503
- additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
5504
- }
5505
- };
5516
+ return shouldInject ? { kind: "context", context: briefText } : NOOP;
5506
5517
  }
5507
5518
  async function preTool(input, options) {
5508
5519
  const original = { ...input.tool_input || {} };
@@ -5527,12 +5538,7 @@ async function preTool(input, options) {
5527
5538
  if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
5528
5539
  updatedInput.expected_version = ledger.taskVersion;
5529
5540
  }
5530
- return {
5531
- hookSpecificOutput: {
5532
- hookEventName: "PreToolUse",
5533
- updatedInput
5534
- }
5535
- };
5541
+ return { kind: "rewrite", updatedInput };
5536
5542
  }
5537
5543
  async function postTool(input, client2, options) {
5538
5544
  const name = input.tool_name || "";
@@ -5543,7 +5549,7 @@ async function postTool(input, client2, options) {
5543
5549
  let heartbeat;
5544
5550
  withLedger(input.session_id, options, (ledger) => {
5545
5551
  ledger.materialToolCalls += 1;
5546
- if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
5552
+ if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name) || name === "Bash" && action !== "read") {
5547
5553
  ledger.mutatingOperations += 1;
5548
5554
  }
5549
5555
  if (name.includes("mindgraph_capture") && !payload?.error) {
@@ -5583,7 +5589,7 @@ async function postTool(input, client2, options) {
5583
5589
  expected_version: heartbeat.taskVersion,
5584
5590
  lease_epoch: heartbeat.leaseEpoch,
5585
5591
  idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
5586
- agent_id: options.agentId || "claude-code"
5592
+ agent_id: options.agentId || options.harness
5587
5593
  })
5588
5594
  );
5589
5595
  withLedger(input.session_id, options, (ledger) => {
@@ -5593,24 +5599,25 @@ async function postTool(input, client2, options) {
5593
5599
  } catch {
5594
5600
  }
5595
5601
  }
5596
- return {};
5602
+ return NOOP;
5597
5603
  }
5598
5604
  function stop(input, options) {
5599
- if (input.stop_hook_active) return {};
5605
+ if (input.stop_hook_active) return NOOP;
5600
5606
  return withLedger(input.session_id, options, (ledger) => {
5601
5607
  const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
5602
5608
  const substantial = Boolean(ledger.executionUid) || changed;
5603
5609
  if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
5604
- return {};
5610
+ return NOOP;
5605
5611
  }
5606
5612
  ledger.stopNudged = true;
5607
5613
  return {
5608
- decision: "block",
5614
+ kind: "block",
5609
5615
  reason: "Substantial work is still uncheckpointed. Checkpoint the active iteration, capture one durable lesson/decision/risk, or explicitly state that nothing durable changed. If a local harness task should become authoritative outside this list, promote it once with a stable idempotency key; otherwise do not copy it."
5610
5616
  };
5611
5617
  });
5612
5618
  }
5613
5619
  async function sessionEnd(input, client2, options) {
5620
+ const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
5614
5621
  const ledger = readLedger(input.session_id, options);
5615
5622
  if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
5616
5623
  try {
@@ -5622,9 +5629,9 @@ async function sessionEnd(input, client2, options) {
5622
5629
  expected_version: ledger.taskVersion,
5623
5630
  lease_epoch: ledger.leaseEpoch,
5624
5631
  release_lease: true,
5625
- summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
5632
+ summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
5626
5633
  idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
5627
- agent_id: options.agentId || "claude-code"
5634
+ agent_id: options.agentId || options.harness
5628
5635
  });
5629
5636
  } catch {
5630
5637
  }
@@ -5634,19 +5641,19 @@ async function sessionEnd(input, client2, options) {
5634
5641
  await client2.session({
5635
5642
  action: "close",
5636
5643
  session_uid: ledger.sessionUid,
5637
- agent_id: options.agentId || "claude-code"
5644
+ agent_id: options.agentId || options.harness
5638
5645
  });
5639
5646
  } catch {
5640
5647
  }
5641
5648
  }
5642
- return {};
5649
+ return NOOP;
5643
5650
  }
5644
- async function runClaudeHook(input, client2, options = {}) {
5651
+ async function runHookCore(input, client2, options) {
5645
5652
  switch (input.hook_event_name) {
5646
5653
  case "SessionStart":
5647
5654
  return sessionStart(input, client2, options);
5648
5655
  case "PreToolUse":
5649
- if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
5656
+ if (!input.tool_name?.startsWith("mcp__mindgraph__")) return NOOP;
5650
5657
  return preTool(input, options);
5651
5658
  case "PostToolUse":
5652
5659
  return postTool(input, client2, options);
@@ -5655,19 +5662,19 @@ async function runClaudeHook(input, client2, options = {}) {
5655
5662
  ledger.nativeTasksCreated += 1;
5656
5663
  return void 0;
5657
5664
  });
5658
- return {};
5665
+ return NOOP;
5659
5666
  case "TaskCompleted":
5660
5667
  withLedger(input.session_id, options, (ledger) => {
5661
5668
  ledger.nativeTasksTerminal += 1;
5662
5669
  return void 0;
5663
5670
  });
5664
- return {};
5671
+ return NOOP;
5665
5672
  case "Stop":
5666
5673
  return stop(input, options);
5667
5674
  case "SessionEnd":
5668
5675
  return sessionEnd(input, client2, options);
5669
5676
  default:
5670
- return {};
5677
+ return NOOP;
5671
5678
  }
5672
5679
  }
5673
5680
  async function readHookInput(stream = process.stdin) {
@@ -5678,6 +5685,68 @@ async function readHookInput(stream = process.stdin) {
5678
5685
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
5679
5686
  }
5680
5687
 
5688
+ // src/claude-hooks.ts
5689
+ async function runClaudeHook(input, client2, options = {}) {
5690
+ const output = await runHookCore(input, client2, {
5691
+ ...options,
5692
+ harness: "claude-code",
5693
+ // D10: compaction is a new Claude context boundary, so the same brief must
5694
+ // be re-injected even when its content hash is unchanged.
5695
+ reinjectUnchangedCompact: true
5696
+ });
5697
+ switch (output.kind) {
5698
+ case "context":
5699
+ return {
5700
+ hookSpecificOutput: {
5701
+ hookEventName: "SessionStart",
5702
+ additionalContext: output.context
5703
+ }
5704
+ };
5705
+ case "rewrite":
5706
+ return {
5707
+ hookSpecificOutput: {
5708
+ hookEventName: "PreToolUse",
5709
+ updatedInput: output.updatedInput
5710
+ }
5711
+ };
5712
+ case "block":
5713
+ return { decision: "block", reason: output.reason };
5714
+ case "noop":
5715
+ return {};
5716
+ }
5717
+ }
5718
+
5719
+ // src/codex-hooks.ts
5720
+ async function runCodexHook(input, client2, options = {}) {
5721
+ const output = await runHookCore(input, client2, {
5722
+ ...options,
5723
+ harness: "codex",
5724
+ // B7's native contract suppresses repeated unchanged brief injections.
5725
+ reinjectUnchangedCompact: false
5726
+ });
5727
+ switch (output.kind) {
5728
+ case "context":
5729
+ return {
5730
+ hookSpecificOutput: {
5731
+ hookEventName: "SessionStart",
5732
+ additionalContext: output.context
5733
+ }
5734
+ };
5735
+ case "rewrite":
5736
+ return {
5737
+ hookSpecificOutput: {
5738
+ hookEventName: "PreToolUse",
5739
+ permissionDecision: "allow",
5740
+ updatedInput: output.updatedInput
5741
+ }
5742
+ };
5743
+ case "block":
5744
+ return { decision: "block", reason: output.reason };
5745
+ case "noop":
5746
+ return {};
5747
+ }
5748
+ }
5749
+
5681
5750
  // src/hook-installer.ts
5682
5751
  var import_node_fs4 = __toESM(require("fs"));
5683
5752
  var import_node_os4 = __toESM(require("os"));
@@ -5685,59 +5754,78 @@ var import_node_path4 = __toESM(require("path"));
5685
5754
  var import_node_crypto3 = __toESM(require("crypto"));
5686
5755
  var OWNER_MARKER = "--owner mindgraph";
5687
5756
  var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
5688
- var COMMAND = `node "$HOME/${RUNNER_RELATIVE}" hook --harness claude-code --owner mindgraph`;
5757
+ function command(harness) {
5758
+ return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
5759
+ }
5760
+ function windowsCommand(harness) {
5761
+ return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
5762
+ }
5689
5763
  function installHookRunner(sourceFile, homeDir) {
5690
5764
  const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
5691
5765
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
5692
5766
  import_node_fs4.default.copyFileSync(sourceFile, target);
5693
5767
  return target;
5694
5768
  }
5695
- function uninstallHookRunner(homeDir) {
5696
- try {
5697
- import_node_fs4.default.rmSync(import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE));
5698
- } catch {
5769
+ function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex")) {
5770
+ if (harness === "codex") {
5771
+ return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
5699
5772
  }
5700
- }
5701
- function settingsPath(scope, projectDir = process.cwd()) {
5702
5773
  return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
5703
5774
  }
5704
5775
  function readSettings(file) {
5705
5776
  if (!import_node_fs4.default.existsSync(file)) return {};
5706
5777
  return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
5707
5778
  }
5708
- function ownedHook(timeout) {
5779
+ function ownedHook(harness, timeout, additionalContextLimit) {
5709
5780
  return {
5710
5781
  type: "command",
5711
- command: COMMAND,
5782
+ command: command(harness),
5783
+ ...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
5712
5784
  timeout,
5713
- statusMessage: "Syncing MindGraph session context"
5785
+ statusMessage: "Syncing MindGraph session context",
5786
+ ...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
5714
5787
  };
5715
5788
  }
5716
- function desiredEntries() {
5717
- return {
5789
+ function desiredEntries(harness) {
5790
+ const common = {
5718
5791
  SessionStart: [
5719
5792
  {
5720
- matcher: "startup|resume|clear|compact|fork",
5721
- hooks: [ownedHook(20)]
5793
+ matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
5794
+ // B7 live acceptance caught cold SessionStart calls crossing the
5795
+ // inherited 20s edge in both harnesses: the hooks correctly failed
5796
+ // open, but no brief reached the model. The same installed runner
5797
+ // completed in 16.84s once warm. Keep a 30s cold-tenant margin.
5798
+ hooks: [
5799
+ ownedHook(
5800
+ harness,
5801
+ 30,
5802
+ harness === "codex" ? 3e3 : void 0
5803
+ )
5804
+ ]
5722
5805
  }
5723
5806
  ],
5724
5807
  PreToolUse: [
5725
5808
  {
5726
5809
  matcher: "mcp__mindgraph__.*",
5727
- hooks: [ownedHook(3)]
5810
+ hooks: [ownedHook(harness, 3)]
5728
5811
  }
5729
5812
  ],
5730
- PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
5731
- TaskCreated: [{ hooks: [ownedHook(3)] }],
5732
- TaskCompleted: [{ hooks: [ownedHook(3)] }],
5733
- Stop: [{ hooks: [ownedHook(5)] }],
5734
- SessionEnd: [{ hooks: [ownedHook(8)] }]
5813
+ PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 5)] }],
5814
+ Stop: [{ hooks: [ownedHook(harness, 5)] }],
5815
+ SessionEnd: [
5816
+ { hooks: [ownedHook(harness, harness === "codex" ? 3 : 8)] }
5817
+ ]
5735
5818
  };
5819
+ return harness === "claude-code" ? {
5820
+ ...common,
5821
+ TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
5822
+ TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
5823
+ } : common;
5736
5824
  }
5737
5825
  function hookCommand(value) {
5738
5826
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5739
- const command = value.command;
5740
- return typeof command === "string" ? command : void 0;
5827
+ const command2 = value.command;
5828
+ return typeof command2 === "string" ? command2 : void 0;
5741
5829
  }
5742
5830
  function entryHasOwnedHook(value) {
5743
5831
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -5753,13 +5841,13 @@ function writeSettings(file, settings) {
5753
5841
  });
5754
5842
  import_node_fs4.default.renameSync(temporary, file);
5755
5843
  }
5756
- function installClaudeHooks(scope, projectDir = process.cwd()) {
5757
- const file = settingsPath(scope, projectDir);
5844
+ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5845
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5758
5846
  const settings = readSettings(file);
5759
5847
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
5760
5848
  let added = 0;
5761
5849
  let updated = 0;
5762
- for (const [event, wanted] of Object.entries(desiredEntries())) {
5850
+ for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
5763
5851
  const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
5764
5852
  const kept = current.filter((entry) => !entryHasOwnedHook(entry));
5765
5853
  const hadOwned = kept.length !== current.length;
@@ -5774,8 +5862,8 @@ function installClaudeHooks(scope, projectDir = process.cwd()) {
5774
5862
  writeSettings(file, settings);
5775
5863
  return { path: file, added, updated };
5776
5864
  }
5777
- function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5778
- const file = settingsPath(scope, projectDir);
5865
+ function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5866
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5779
5867
  if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
5780
5868
  const settings = readSettings(file);
5781
5869
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
@@ -5810,6 +5898,18 @@ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5810
5898
  writeSettings(file, settings);
5811
5899
  return { path: file, removed };
5812
5900
  }
5901
+ function installClaudeHooks(scope, projectDir = process.cwd()) {
5902
+ return installHooks("claude-code", scope, projectDir);
5903
+ }
5904
+ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5905
+ return uninstallHooks("claude-code", scope, projectDir);
5906
+ }
5907
+ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
5908
+ return installHooks("codex", scope, projectDir, codexHomeDir);
5909
+ }
5910
+ function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
5911
+ return uninstallHooks("codex", scope, projectDir, codexHomeDir);
5912
+ }
5813
5913
 
5814
5914
  // src/cli.ts
5815
5915
  function getClaudeDesktopConfigPath() {
@@ -6019,8 +6119,8 @@ USAGE:
6019
6119
  mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
6020
6120
  mindgraph-mcp uninstall Remove from Claude Desktop config
6021
6121
  mindgraph-mcp uninstall-code Remove from Claude Code
6022
- mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
6023
- mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
6122
+ mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
6123
+ mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
6024
6124
  mindgraph-mcp status Show installation status
6025
6125
 
6026
6126
  OPTIONS:
@@ -6028,7 +6128,7 @@ OPTIONS:
6028
6128
  --base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
6029
6129
  --scope <scope> Hook settings scope: project (default) or user
6030
6130
  --project-dir <dir> Project root for project-scoped hooks
6031
- --harness <name> Hook harness (currently claude-code)
6131
+ --harness <name> Hook harness (claude-code or codex)
6032
6132
  --help, -h Show this help message
6033
6133
 
6034
6134
  EXAMPLES:
@@ -6166,8 +6266,8 @@ async function interactiveInit(baseUrl) {
6166
6266
  console.log("\nDone! Your knowledge graph is now connected.");
6167
6267
  }
6168
6268
  async function main2() {
6169
- const { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6170
- switch (command) {
6269
+ const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6270
+ switch (command2) {
6171
6271
  case "help":
6172
6272
  printUsage();
6173
6273
  break;
@@ -6200,14 +6300,15 @@ async function main2() {
6200
6300
  uninstallClaudeCode();
6201
6301
  break;
6202
6302
  case "install-hooks": {
6203
- if ((harness || "claude-code") !== "claude-code") {
6204
- throw new Error("only --harness claude-code is available in this release");
6303
+ const hookHarness = harness || "claude-code";
6304
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6305
+ throw new Error("--harness must be claude-code or codex");
6205
6306
  }
6206
6307
  const runner = installHookRunner(__filename);
6207
6308
  console.log(`Installed pinned hook runner at ${runner}`);
6208
- const result = installClaudeHooks(scope, projectDir);
6309
+ const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
6209
6310
  console.log(
6210
- `Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
6311
+ `Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
6211
6312
  );
6212
6313
  printCodegraphStatus();
6213
6314
  if (apiKey || baseUrl || agentId) {
@@ -6221,18 +6322,22 @@ async function main2() {
6221
6322
  break;
6222
6323
  }
6223
6324
  case "uninstall-hooks": {
6224
- if ((harness || "claude-code") !== "claude-code") {
6225
- throw new Error("only --harness claude-code is available in this release");
6325
+ const hookHarness = harness || "claude-code";
6326
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6327
+ throw new Error("--harness must be claude-code or codex");
6226
6328
  }
6227
- uninstallHookRunner();
6228
- const result = uninstallClaudeHooks(scope, projectDir);
6329
+ const result = hookHarness === "codex" ? uninstallCodexHooks(scope, projectDir) : uninstallClaudeHooks(scope, projectDir);
6229
6330
  console.log(
6230
- `Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
6331
+ `Removed ${result.removed} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hooks from ${result.path}`
6231
6332
  );
6232
6333
  break;
6233
6334
  }
6234
6335
  case "hook": {
6235
6336
  const stored = loadHookEnv();
6337
+ const hookHarness = harness || "claude-code";
6338
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6339
+ throw new Error("--harness must be claude-code or codex");
6340
+ }
6236
6341
  const hookApiKey = apiKey ?? stored.apiKey;
6237
6342
  const hookBaseUrl = baseUrl ?? stored.baseUrl;
6238
6343
  if (!hookApiKey) {
@@ -6248,9 +6353,18 @@ async function main2() {
6248
6353
  maxRetries: 0,
6249
6354
  telemetrySurface: "mcp"
6250
6355
  });
6251
- const output = await runClaudeHook(input, client2, {
6252
- agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || "claude-code"
6253
- });
6356
+ const hookOptions = {
6357
+ agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || hookHarness
6358
+ };
6359
+ const output = hookHarness === "codex" ? await runCodexHook(
6360
+ input,
6361
+ client2,
6362
+ hookOptions
6363
+ ) : await runClaudeHook(
6364
+ input,
6365
+ client2,
6366
+ hookOptions
6367
+ );
6254
6368
  process.stdout.write(`${JSON.stringify(output)}
6255
6369
  `);
6256
6370
  } catch {
@@ -6266,7 +6380,7 @@ async function main2() {
6266
6380
  break;
6267
6381
  }
6268
6382
  default:
6269
- console.error(`Unknown command: ${command}`);
6383
+ console.error(`Unknown command: ${command2}`);
6270
6384
  printUsage();
6271
6385
  process.exit(1);
6272
6386
  }
package/dist/index.js CHANGED
@@ -3955,7 +3955,7 @@ if (PROFILE === "coding") {
3955
3955
  var server = new import_server.Server(
3956
3956
  {
3957
3957
  name: "mindgraph",
3958
- version: "0.14.7"
3958
+ version: "0.14.8"
3959
3959
  },
3960
3960
  {
3961
3961
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindgraph-mcp",
3
- "version": "0.14.7",
3
+ "version": "0.14.8",
4
4
  "description": "MCP server for MindGraph — connect Claude Desktop, Claude Code, and any MCP client to your knowledge graph",
5
5
  "repository": {
6
6
  "type": "git",