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 +28 -7
- package/dist/cli.js +213 -90
- package/dist/index.js +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mindgraph-mcp)
|
|
4
4
|
[](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.
|
|
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
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
5227
|
+
command2 = "help";
|
|
5219
5228
|
break;
|
|
5220
5229
|
default:
|
|
5221
5230
|
if (!arg.startsWith("-")) {
|
|
5222
|
-
|
|
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/
|
|
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
|
-
|
|
5257
|
+
var NOOP = { kind: "noop" };
|
|
5258
|
+
function defaultLedger(sessionId, options) {
|
|
5249
5259
|
return {
|
|
5250
|
-
harness:
|
|
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
|
|
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:
|
|
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 ||
|
|
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:
|
|
5434
|
-
harness:
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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:
|
|
5632
|
+
summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
|
|
5617
5633
|
idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
|
|
5618
|
-
agent_id: options.agentId ||
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
|
5687
|
-
|
|
5688
|
-
|
|
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:
|
|
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
|
-
|
|
5789
|
+
function desiredEntries(harness) {
|
|
5790
|
+
const common = {
|
|
5709
5791
|
SessionStart: [
|
|
5710
5792
|
{
|
|
5711
|
-
matcher: "startup|resume|clear|compact|fork",
|
|
5712
|
-
|
|
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
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
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
|
|
5731
|
-
return typeof
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
6195
|
-
|
|
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
|
-
|
|
6216
|
-
|
|
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
|
-
|
|
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
|
|
6243
|
-
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId ||
|
|
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: ${
|
|
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.
|
|
3958
|
+
version: "0.14.8"
|
|
3950
3959
|
},
|
|
3951
3960
|
{
|
|
3952
3961
|
capabilities: {
|