mindgraph-mcp 0.14.6 → 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
@@ -2891,6 +2891,7 @@ async function handleRetrieve(client2, args) {
2891
2891
  limit,
2892
2892
  offset,
2893
2893
  include_chunks,
2894
+ include_documents,
2894
2895
  include_graph,
2895
2896
  graph_expansion_limit,
2896
2897
  graph_max_depth,
@@ -2908,6 +2909,10 @@ async function handleRetrieve(client2, args) {
2908
2909
  node_types,
2909
2910
  layer,
2910
2911
  chunk_limit: include_chunks ? limit ?? 5 : 0,
2912
+ // Scoped by default, never walled: the coding profile drops the
2913
+ // document/article leg (the "Bob Work" class of noise) unless the
2914
+ // model opts in for a genuinely document-shaped question.
2915
+ ...process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
2911
2916
  include_graph,
2912
2917
  graph_expansion_limit: graph_expansion_limit ?? 3,
2913
2918
  graph_max_depth: graph_max_depth ?? 2
@@ -3940,6 +3945,10 @@ var init_tools = __esm({
3940
3945
  type: "number",
3941
3946
  description: "Pagination offset (for stale_derivations)"
3942
3947
  },
3948
+ include_documents: {
3949
+ type: "boolean",
3950
+ description: "Include ingested documents and wiki articles in context results. The coding profile defaults these OFF so engineering questions aren't answered from unrelated ingested content; pass true when the question is about a document, spec, or article."
3951
+ },
3943
3952
  include_chunks: {
3944
3953
  type: "boolean",
3945
3954
  description: "Include source document chunks in context results (default: false \u2014 set true to fetch full chunk text for citations or deep reading)"
@@ -4908,7 +4917,7 @@ Capture knowledge when the user shares something worth remembering:
4908
4917
  server = new import_server.Server(
4909
4918
  {
4910
4919
  name: "mindgraph",
4911
- version: "0.14.6"
4920
+ version: "0.14.8"
4912
4921
  },
4913
4922
  {
4914
4923
  capabilities: {
@@ -5173,7 +5182,7 @@ function saveHookEnv(env, dir) {
5173
5182
  // src/cli-args.ts
5174
5183
  function parseArgs(argv) {
5175
5184
  const args = argv.slice(2);
5176
- let command = "serve";
5185
+ let command2 = "serve";
5177
5186
  let apiKey = process.env.MINDGRAPH_API_KEY;
5178
5187
  let baseUrl = process.env.MINDGRAPH_BASE_URL;
5179
5188
  let scope = "project";
@@ -5215,16 +5224,16 @@ function parseArgs(argv) {
5215
5224
  break;
5216
5225
  case "--help":
5217
5226
  case "-h":
5218
- command = "help";
5227
+ command2 = "help";
5219
5228
  break;
5220
5229
  default:
5221
5230
  if (!arg.startsWith("-")) {
5222
- command = arg;
5231
+ command2 = arg;
5223
5232
  }
5224
5233
  break;
5225
5234
  }
5226
5235
  }
5227
- return { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5236
+ return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5228
5237
  }
5229
5238
  function mcpAddFailureOutput(e) {
5230
5239
  const err5 = e;
@@ -5238,16 +5247,17 @@ function classifyMcpAddFailure(e) {
5238
5247
  return "other";
5239
5248
  }
5240
5249
 
5241
- // src/claude-hooks.ts
5250
+ // src/hook-core.ts
5242
5251
  var import_node_crypto2 = __toESM(require("crypto"));
5243
5252
  var import_node_fs3 = __toESM(require("fs"));
5244
5253
  var import_node_os3 = __toESM(require("os"));
5245
5254
  var import_node_path3 = __toESM(require("path"));
5246
5255
  var import_node_child_process2 = require("child_process");
5247
5256
  init_codegraph();
5248
- function defaultLedger(sessionId) {
5257
+ var NOOP = { kind: "noop" };
5258
+ function defaultLedger(sessionId, options) {
5249
5259
  return {
5250
- harness: "claude-code",
5260
+ harness: options.harness,
5251
5261
  harnessSessionId: sessionId,
5252
5262
  materialToolCalls: 0,
5253
5263
  mutatingOperations: 0,
@@ -5264,7 +5274,8 @@ function runtimeDir(options) {
5264
5274
  return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
5265
5275
  }
5266
5276
  function ledgerPath(sessionId, options) {
5267
- 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");
5268
5279
  return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
5269
5280
  }
5270
5281
  function waitBriefly(ms) {
@@ -5293,10 +5304,10 @@ function withLedger(sessionId, options, update) {
5293
5304
  }
5294
5305
  }
5295
5306
  if (lockFd === void 0) {
5296
- return update(defaultLedger(sessionId));
5307
+ return update(defaultLedger(sessionId, options));
5297
5308
  }
5298
5309
  try {
5299
- let ledger = defaultLedger(sessionId);
5310
+ let ledger = defaultLedger(sessionId, options);
5300
5311
  try {
5301
5312
  ledger = {
5302
5313
  ...ledger,
@@ -5347,8 +5358,9 @@ async function invocationContext(input, options) {
5347
5358
  workspaceFile: options.workspaceFile
5348
5359
  }).resolveRepository();
5349
5360
  return {
5350
- harness: "claude-code",
5361
+ harness: options.harness,
5351
5362
  harnessSessionId: input.session_id,
5363
+ harnessTurnId: input.turn_id,
5352
5364
  cwd: input.cwd,
5353
5365
  repoId: repository.repoId,
5354
5366
  branch: gitValue(input.cwd, ["branch", "--show-current"]),
@@ -5425,13 +5437,21 @@ async function repositoryScopeUids(input, client2, options, agentId) {
5425
5437
  )
5426
5438
  ];
5427
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
+ }
5428
5447
  async function sessionStart(input, client2, options) {
5429
- 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";
5430
5450
  const opened = object(
5431
5451
  await client2.session({
5432
5452
  action: "open",
5433
- label: `Claude Code ${input.session_id}`,
5434
- harness: "claude-code",
5453
+ label: `${harnessLabel} ${input.session_id}`,
5454
+ harness: options.harness,
5435
5455
  harness_session_id: input.session_id,
5436
5456
  model: input.model,
5437
5457
  agent_id: agentId
@@ -5449,7 +5469,7 @@ async function sessionStart(input, client2, options) {
5449
5469
  );
5450
5470
  const task = object(brief?.task);
5451
5471
  const lease = object(brief?.lease);
5452
- if (sessionUid && string(task?.uid) && number(task?.version)) {
5472
+ if (sessionUid && string(task?.uid) && number(task?.version) && isOwnPriorWork(brief, agentId)) {
5453
5473
  try {
5454
5474
  const claimed = object(
5455
5475
  await client2.plan({
@@ -5474,7 +5494,11 @@ async function sessionStart(input, client2, options) {
5474
5494
  } catch {
5475
5495
  }
5476
5496
  }
5477
- 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";
5478
5502
  ledger.sessionUid = sessionUid;
5479
5503
  ledger.baselineWorktree = worktreeState(input.cwd);
5480
5504
  const selectedTask = object(brief?.task);
@@ -5486,14 +5510,10 @@ async function sessionStart(input, client2, options) {
5486
5510
  ledger.leaseEpoch = number(selectedLease?.lease_epoch);
5487
5511
  ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
5488
5512
  ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
5489
- return void 0;
5513
+ ledger.lastBriefHash = briefHash;
5514
+ return changed || reinjectCompact;
5490
5515
  });
5491
- return {
5492
- hookSpecificOutput: {
5493
- hookEventName: "SessionStart",
5494
- additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
5495
- }
5496
- };
5516
+ return shouldInject ? { kind: "context", context: briefText } : NOOP;
5497
5517
  }
5498
5518
  async function preTool(input, options) {
5499
5519
  const original = { ...input.tool_input || {} };
@@ -5518,12 +5538,7 @@ async function preTool(input, options) {
5518
5538
  if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
5519
5539
  updatedInput.expected_version = ledger.taskVersion;
5520
5540
  }
5521
- return {
5522
- hookSpecificOutput: {
5523
- hookEventName: "PreToolUse",
5524
- updatedInput
5525
- }
5526
- };
5541
+ return { kind: "rewrite", updatedInput };
5527
5542
  }
5528
5543
  async function postTool(input, client2, options) {
5529
5544
  const name = input.tool_name || "";
@@ -5534,7 +5549,7 @@ async function postTool(input, client2, options) {
5534
5549
  let heartbeat;
5535
5550
  withLedger(input.session_id, options, (ledger) => {
5536
5551
  ledger.materialToolCalls += 1;
5537
- if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
5552
+ if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name) || name === "Bash" && action !== "read") {
5538
5553
  ledger.mutatingOperations += 1;
5539
5554
  }
5540
5555
  if (name.includes("mindgraph_capture") && !payload?.error) {
@@ -5574,7 +5589,7 @@ async function postTool(input, client2, options) {
5574
5589
  expected_version: heartbeat.taskVersion,
5575
5590
  lease_epoch: heartbeat.leaseEpoch,
5576
5591
  idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
5577
- agent_id: options.agentId || "claude-code"
5592
+ agent_id: options.agentId || options.harness
5578
5593
  })
5579
5594
  );
5580
5595
  withLedger(input.session_id, options, (ledger) => {
@@ -5584,24 +5599,25 @@ async function postTool(input, client2, options) {
5584
5599
  } catch {
5585
5600
  }
5586
5601
  }
5587
- return {};
5602
+ return NOOP;
5588
5603
  }
5589
5604
  function stop(input, options) {
5590
- if (input.stop_hook_active) return {};
5605
+ if (input.stop_hook_active) return NOOP;
5591
5606
  return withLedger(input.session_id, options, (ledger) => {
5592
5607
  const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
5593
5608
  const substantial = Boolean(ledger.executionUid) || changed;
5594
5609
  if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
5595
- return {};
5610
+ return NOOP;
5596
5611
  }
5597
5612
  ledger.stopNudged = true;
5598
5613
  return {
5599
- decision: "block",
5614
+ kind: "block",
5600
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."
5601
5616
  };
5602
5617
  });
5603
5618
  }
5604
5619
  async function sessionEnd(input, client2, options) {
5620
+ const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
5605
5621
  const ledger = readLedger(input.session_id, options);
5606
5622
  if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
5607
5623
  try {
@@ -5613,9 +5629,9 @@ async function sessionEnd(input, client2, options) {
5613
5629
  expected_version: ledger.taskVersion,
5614
5630
  lease_epoch: ledger.leaseEpoch,
5615
5631
  release_lease: true,
5616
- summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
5632
+ summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
5617
5633
  idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
5618
- agent_id: options.agentId || "claude-code"
5634
+ agent_id: options.agentId || options.harness
5619
5635
  });
5620
5636
  } catch {
5621
5637
  }
@@ -5625,19 +5641,19 @@ async function sessionEnd(input, client2, options) {
5625
5641
  await client2.session({
5626
5642
  action: "close",
5627
5643
  session_uid: ledger.sessionUid,
5628
- agent_id: options.agentId || "claude-code"
5644
+ agent_id: options.agentId || options.harness
5629
5645
  });
5630
5646
  } catch {
5631
5647
  }
5632
5648
  }
5633
- return {};
5649
+ return NOOP;
5634
5650
  }
5635
- async function runClaudeHook(input, client2, options = {}) {
5651
+ async function runHookCore(input, client2, options) {
5636
5652
  switch (input.hook_event_name) {
5637
5653
  case "SessionStart":
5638
5654
  return sessionStart(input, client2, options);
5639
5655
  case "PreToolUse":
5640
- if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
5656
+ if (!input.tool_name?.startsWith("mcp__mindgraph__")) return NOOP;
5641
5657
  return preTool(input, options);
5642
5658
  case "PostToolUse":
5643
5659
  return postTool(input, client2, options);
@@ -5646,19 +5662,19 @@ async function runClaudeHook(input, client2, options = {}) {
5646
5662
  ledger.nativeTasksCreated += 1;
5647
5663
  return void 0;
5648
5664
  });
5649
- return {};
5665
+ return NOOP;
5650
5666
  case "TaskCompleted":
5651
5667
  withLedger(input.session_id, options, (ledger) => {
5652
5668
  ledger.nativeTasksTerminal += 1;
5653
5669
  return void 0;
5654
5670
  });
5655
- return {};
5671
+ return NOOP;
5656
5672
  case "Stop":
5657
5673
  return stop(input, options);
5658
5674
  case "SessionEnd":
5659
5675
  return sessionEnd(input, client2, options);
5660
5676
  default:
5661
- return {};
5677
+ return NOOP;
5662
5678
  }
5663
5679
  }
5664
5680
  async function readHookInput(stream = process.stdin) {
@@ -5669,6 +5685,68 @@ async function readHookInput(stream = process.stdin) {
5669
5685
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
5670
5686
  }
5671
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
+
5672
5750
  // src/hook-installer.ts
5673
5751
  var import_node_fs4 = __toESM(require("fs"));
5674
5752
  var import_node_os4 = __toESM(require("os"));
@@ -5676,59 +5754,78 @@ var import_node_path4 = __toESM(require("path"));
5676
5754
  var import_node_crypto3 = __toESM(require("crypto"));
5677
5755
  var OWNER_MARKER = "--owner mindgraph";
5678
5756
  var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
5679
- 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
+ }
5680
5763
  function installHookRunner(sourceFile, homeDir) {
5681
5764
  const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
5682
5765
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
5683
5766
  import_node_fs4.default.copyFileSync(sourceFile, target);
5684
5767
  return target;
5685
5768
  }
5686
- function uninstallHookRunner(homeDir) {
5687
- try {
5688
- import_node_fs4.default.rmSync(import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE));
5689
- } 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");
5690
5772
  }
5691
- }
5692
- function settingsPath(scope, projectDir = process.cwd()) {
5693
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");
5694
5774
  }
5695
5775
  function readSettings(file) {
5696
5776
  if (!import_node_fs4.default.existsSync(file)) return {};
5697
5777
  return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
5698
5778
  }
5699
- function ownedHook(timeout) {
5779
+ function ownedHook(harness, timeout, additionalContextLimit) {
5700
5780
  return {
5701
5781
  type: "command",
5702
- command: COMMAND,
5782
+ command: command(harness),
5783
+ ...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
5703
5784
  timeout,
5704
- statusMessage: "Syncing MindGraph session context"
5785
+ statusMessage: "Syncing MindGraph session context",
5786
+ ...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
5705
5787
  };
5706
5788
  }
5707
- function desiredEntries() {
5708
- return {
5789
+ function desiredEntries(harness) {
5790
+ const common = {
5709
5791
  SessionStart: [
5710
5792
  {
5711
- matcher: "startup|resume|clear|compact|fork",
5712
- 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
+ ]
5713
5805
  }
5714
5806
  ],
5715
5807
  PreToolUse: [
5716
5808
  {
5717
5809
  matcher: "mcp__mindgraph__.*",
5718
- hooks: [ownedHook(3)]
5810
+ hooks: [ownedHook(harness, 3)]
5719
5811
  }
5720
5812
  ],
5721
- PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
5722
- TaskCreated: [{ hooks: [ownedHook(3)] }],
5723
- TaskCompleted: [{ hooks: [ownedHook(3)] }],
5724
- Stop: [{ hooks: [ownedHook(5)] }],
5725
- 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
+ ]
5726
5818
  };
5819
+ return harness === "claude-code" ? {
5820
+ ...common,
5821
+ TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
5822
+ TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
5823
+ } : common;
5727
5824
  }
5728
5825
  function hookCommand(value) {
5729
5826
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5730
- const command = value.command;
5731
- return typeof command === "string" ? command : void 0;
5827
+ const command2 = value.command;
5828
+ return typeof command2 === "string" ? command2 : void 0;
5732
5829
  }
5733
5830
  function entryHasOwnedHook(value) {
5734
5831
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -5744,13 +5841,13 @@ function writeSettings(file, settings) {
5744
5841
  });
5745
5842
  import_node_fs4.default.renameSync(temporary, file);
5746
5843
  }
5747
- function installClaudeHooks(scope, projectDir = process.cwd()) {
5748
- const file = settingsPath(scope, projectDir);
5844
+ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5845
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5749
5846
  const settings = readSettings(file);
5750
5847
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
5751
5848
  let added = 0;
5752
5849
  let updated = 0;
5753
- for (const [event, wanted] of Object.entries(desiredEntries())) {
5850
+ for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
5754
5851
  const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
5755
5852
  const kept = current.filter((entry) => !entryHasOwnedHook(entry));
5756
5853
  const hadOwned = kept.length !== current.length;
@@ -5765,8 +5862,8 @@ function installClaudeHooks(scope, projectDir = process.cwd()) {
5765
5862
  writeSettings(file, settings);
5766
5863
  return { path: file, added, updated };
5767
5864
  }
5768
- function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5769
- const file = settingsPath(scope, projectDir);
5865
+ function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5866
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5770
5867
  if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
5771
5868
  const settings = readSettings(file);
5772
5869
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
@@ -5801,6 +5898,18 @@ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5801
5898
  writeSettings(file, settings);
5802
5899
  return { path: file, removed };
5803
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
+ }
5804
5913
 
5805
5914
  // src/cli.ts
5806
5915
  function getClaudeDesktopConfigPath() {
@@ -6010,8 +6119,8 @@ USAGE:
6010
6119
  mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
6011
6120
  mindgraph-mcp uninstall Remove from Claude Desktop config
6012
6121
  mindgraph-mcp uninstall-code Remove from Claude Code
6013
- mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
6014
- 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]
6015
6124
  mindgraph-mcp status Show installation status
6016
6125
 
6017
6126
  OPTIONS:
@@ -6019,7 +6128,7 @@ OPTIONS:
6019
6128
  --base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
6020
6129
  --scope <scope> Hook settings scope: project (default) or user
6021
6130
  --project-dir <dir> Project root for project-scoped hooks
6022
- --harness <name> Hook harness (currently claude-code)
6131
+ --harness <name> Hook harness (claude-code or codex)
6023
6132
  --help, -h Show this help message
6024
6133
 
6025
6134
  EXAMPLES:
@@ -6157,8 +6266,8 @@ async function interactiveInit(baseUrl) {
6157
6266
  console.log("\nDone! Your knowledge graph is now connected.");
6158
6267
  }
6159
6268
  async function main2() {
6160
- const { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6161
- switch (command) {
6269
+ const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6270
+ switch (command2) {
6162
6271
  case "help":
6163
6272
  printUsage();
6164
6273
  break;
@@ -6191,14 +6300,15 @@ async function main2() {
6191
6300
  uninstallClaudeCode();
6192
6301
  break;
6193
6302
  case "install-hooks": {
6194
- if ((harness || "claude-code") !== "claude-code") {
6195
- 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");
6196
6306
  }
6197
6307
  const runner = installHookRunner(__filename);
6198
6308
  console.log(`Installed pinned hook runner at ${runner}`);
6199
- const result = installClaudeHooks(scope, projectDir);
6309
+ const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
6200
6310
  console.log(
6201
- `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}`
6202
6312
  );
6203
6313
  printCodegraphStatus();
6204
6314
  if (apiKey || baseUrl || agentId) {
@@ -6212,18 +6322,22 @@ async function main2() {
6212
6322
  break;
6213
6323
  }
6214
6324
  case "uninstall-hooks": {
6215
- if ((harness || "claude-code") !== "claude-code") {
6216
- 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");
6217
6328
  }
6218
- uninstallHookRunner();
6219
- const result = uninstallClaudeHooks(scope, projectDir);
6329
+ const result = hookHarness === "codex" ? uninstallCodexHooks(scope, projectDir) : uninstallClaudeHooks(scope, projectDir);
6220
6330
  console.log(
6221
- `Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
6331
+ `Removed ${result.removed} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hooks from ${result.path}`
6222
6332
  );
6223
6333
  break;
6224
6334
  }
6225
6335
  case "hook": {
6226
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
+ }
6227
6341
  const hookApiKey = apiKey ?? stored.apiKey;
6228
6342
  const hookBaseUrl = baseUrl ?? stored.baseUrl;
6229
6343
  if (!hookApiKey) {
@@ -6239,9 +6353,18 @@ async function main2() {
6239
6353
  maxRetries: 0,
6240
6354
  telemetrySurface: "mcp"
6241
6355
  });
6242
- const output = await runClaudeHook(input, client2, {
6243
- agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || "claude-code"
6244
- });
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
+ );
6245
6368
  process.stdout.write(`${JSON.stringify(output)}
6246
6369
  `);
6247
6370
  } catch {
@@ -6257,7 +6380,7 @@ async function main2() {
6257
6380
  break;
6258
6381
  }
6259
6382
  default:
6260
- console.error(`Unknown command: ${command}`);
6383
+ console.error(`Unknown command: ${command2}`);
6261
6384
  printUsage();
6262
6385
  process.exit(1);
6263
6386
  }
package/dist/index.js CHANGED
@@ -2060,6 +2060,10 @@ var TOOLS = [
2060
2060
  type: "number",
2061
2061
  description: "Pagination offset (for stale_derivations)"
2062
2062
  },
2063
+ include_documents: {
2064
+ type: "boolean",
2065
+ description: "Include ingested documents and wiki articles in context results. The coding profile defaults these OFF so engineering questions aren't answered from unrelated ingested content; pass true when the question is about a document, spec, or article."
2066
+ },
2063
2067
  include_chunks: {
2064
2068
  type: "boolean",
2065
2069
  description: "Include source document chunks in context results (default: false \u2014 set true to fetch full chunk text for citations or deep reading)"
@@ -2923,6 +2927,7 @@ async function handleRetrieve(client2, args) {
2923
2927
  limit,
2924
2928
  offset,
2925
2929
  include_chunks,
2930
+ include_documents,
2926
2931
  include_graph,
2927
2932
  graph_expansion_limit,
2928
2933
  graph_max_depth,
@@ -2940,6 +2945,10 @@ async function handleRetrieve(client2, args) {
2940
2945
  node_types,
2941
2946
  layer,
2942
2947
  chunk_limit: include_chunks ? limit ?? 5 : 0,
2948
+ // Scoped by default, never walled: the coding profile drops the
2949
+ // document/article leg (the "Bob Work" class of noise) unless the
2950
+ // model opts in for a genuinely document-shaped question.
2951
+ ...process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
2943
2952
  include_graph,
2944
2953
  graph_expansion_limit: graph_expansion_limit ?? 3,
2945
2954
  graph_max_depth: graph_max_depth ?? 2
@@ -3946,7 +3955,7 @@ if (PROFILE === "coding") {
3946
3955
  var server = new import_server.Server(
3947
3956
  {
3948
3957
  name: "mindgraph",
3949
- version: "0.14.6"
3958
+ version: "0.14.8"
3950
3959
  },
3951
3960
  {
3952
3961
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindgraph-mcp",
3
- "version": "0.14.6",
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",