mindgraph-mcp 0.14.9 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +1112 -205
  3. package/dist/index.js +600 -94
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28,6 +28,38 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28
28
  var import_types = require("@modelcontextprotocol/sdk/types.js");
29
29
  var import_mindgraph2 = require("mindgraph");
30
30
 
31
+ // src/hook-env.ts
32
+ var import_node_crypto = __toESM(require("crypto"));
33
+ var import_node_fs = __toESM(require("fs"));
34
+ var import_node_os = __toESM(require("os"));
35
+ var import_node_path = __toESM(require("path"));
36
+ function stableAgentId() {
37
+ let user = "user";
38
+ try {
39
+ user = import_node_os.default.userInfo().username || "user";
40
+ } catch {
41
+ }
42
+ const digest = import_node_crypto.default.createHash("sha256").update(`${user}@${import_node_os.default.hostname()}`).digest("hex").slice(0, 12);
43
+ return `u-${digest}`;
44
+ }
45
+ function hookEnvPath(dir) {
46
+ return import_node_path.default.join(dir || import_node_path.default.join(import_node_os.default.homedir(), ".mindgraph"), "hooks.json");
47
+ }
48
+ function loadHookEnv(dir) {
49
+ try {
50
+ const raw = import_node_fs.default.readFileSync(hookEnvPath(dir), "utf8");
51
+ const parsed = JSON.parse(raw);
52
+ return {
53
+ baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : void 0,
54
+ apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
55
+ agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0,
56
+ orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0
57
+ };
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+
31
63
  // src/error-detail.ts
32
64
  var import_mindgraph = require("mindgraph");
33
65
  function errorDetail(e) {
@@ -37,14 +69,31 @@ function errorDetail(e) {
37
69
  }
38
70
  return e instanceof Error ? e.message : String(e);
39
71
  }
72
+ var CONFLICT_STATE_KEYS = [
73
+ "current_version",
74
+ "current_epoch",
75
+ "lease_expires_at",
76
+ "lease_owner_agent_id"
77
+ ];
78
+ function conflictState(e) {
79
+ if (!(e instanceof import_mindgraph.MindGraphError) || !e.body || typeof e.body !== "object") {
80
+ return {};
81
+ }
82
+ const body = e.body;
83
+ const lifted = {};
84
+ for (const key of CONFLICT_STATE_KEYS) {
85
+ if (body[key] !== void 0) lifted[key] = body[key];
86
+ }
87
+ return lifted;
88
+ }
40
89
 
41
90
  // src/codegraph.ts
42
91
  var import_node_child_process = require("child_process");
43
- var import_node_fs = require("fs");
92
+ var import_node_fs2 = require("fs");
44
93
  var import_promises = require("fs/promises");
45
- var import_node_os = require("os");
46
- var import_node_path = require("path");
47
- var import_node_crypto = require("crypto");
94
+ var import_node_os2 = require("os");
95
+ var import_node_path2 = require("path");
96
+ var import_node_crypto2 = require("crypto");
48
97
  function unavailabilityCaveats(reason, message) {
49
98
  const caveats = [message || "codegraph status failed"];
50
99
  if (reason === "absent") {
@@ -57,14 +106,14 @@ function unavailabilityCaveats(reason, message) {
57
106
  var DEFAULT_TIMEOUT_MS = 5e3;
58
107
  var DEFAULT_STATUS_TTL_MS = 6e4;
59
108
  var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
60
- function normalizePath(path2) {
61
- return path2.replaceAll("\\", "/").replace(/^\.\//, "");
109
+ function normalizePath(path3) {
110
+ return path3.replaceAll("\\", "/").replace(/^\.\//, "");
62
111
  }
63
112
  function normalizeSignature(signature) {
64
113
  return signature.replace(/\s+/g, " ").trim();
65
114
  }
66
- function languageFromPath(path2) {
67
- const extension = (0, import_node_path.extname)(path2).toLowerCase();
115
+ function languageFromPath(path3) {
116
+ const extension = (0, import_node_path2.extname)(path3).toLowerCase();
68
117
  return {
69
118
  ".c": "c",
70
119
  ".cc": "cpp",
@@ -113,30 +162,30 @@ function normalizeRemote(remote) {
113
162
  }
114
163
  }
115
164
  function pathContains(root, candidate) {
116
- const child = (0, import_node_path.relative)(root, candidate);
117
- return child === "" || child !== ".." && !child.startsWith(`..${import_node_path.sep}`) && !(0, import_node_path.isAbsolute)(child);
165
+ const child = (0, import_node_path2.relative)(root, candidate);
166
+ return child === "" || child !== ".." && !child.startsWith(`..${import_node_path2.sep}`) && !(0, import_node_path2.isAbsolute)(child);
118
167
  }
119
168
  function nearestExistingDirectory(candidate) {
120
- let current = (0, import_node_path.resolve)(candidate);
169
+ let current = (0, import_node_path2.resolve)(candidate);
121
170
  for (; ; ) {
122
- if ((0, import_node_fs.existsSync)(current)) {
171
+ if ((0, import_node_fs2.existsSync)(current)) {
123
172
  try {
124
- return (0, import_node_fs.statSync)(current).isDirectory() ? current : (0, import_node_path.dirname)(current);
173
+ return (0, import_node_fs2.statSync)(current).isDirectory() ? current : (0, import_node_path2.dirname)(current);
125
174
  } catch {
126
175
  }
127
176
  }
128
- const parent = (0, import_node_path.dirname)(current);
177
+ const parent = (0, import_node_path2.dirname)(current);
129
178
  if (parent === current) return current;
130
179
  current = parent;
131
180
  }
132
181
  }
133
182
  function findWorkspaceFile(start, explicit) {
134
- if (explicit) return (0, import_node_path.resolve)(start, explicit);
135
- let current = (0, import_node_path.resolve)(start);
183
+ if (explicit) return (0, import_node_path2.resolve)(start, explicit);
184
+ let current = (0, import_node_path2.resolve)(start);
136
185
  for (; ; ) {
137
- const candidate = (0, import_node_path.join)(current, ".mindgraph", "workspace.json");
138
- if ((0, import_node_fs.existsSync)(candidate)) return candidate;
139
- const parent = (0, import_node_path.dirname)(current);
186
+ const candidate = (0, import_node_path2.join)(current, ".mindgraph", "workspace.json");
187
+ if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
188
+ const parent = (0, import_node_path2.dirname)(current);
140
189
  if (parent === current) return void 0;
141
190
  current = parent;
142
191
  }
@@ -223,7 +272,7 @@ var CodegraphAdapter = class {
223
272
  async resolveRepository(repo, fallbackSpaceUid) {
224
273
  const configured = await this.resolveRepositories(fallbackSpaceUid);
225
274
  const selected = repo && configured.find((candidate) => candidate.repoId === repo) || repo && configured.find(
226
- (candidate) => candidate.repoRoot === repo || (0, import_node_path.basename)(candidate.repoRoot) === repo
275
+ (candidate) => candidate.repoRoot === repo || (0, import_node_path2.basename)(candidate.repoRoot) === repo
227
276
  ) || (!repo ? [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, this.cwd)) : void 0) || (!repo && configured.length === 1 ? configured[0] : void 0);
228
277
  if (selected) {
229
278
  return !repo && this.env.MINDGRAPH_REPO_ID ? this.resolveRepositoryIdentity(
@@ -233,8 +282,8 @@ var CodegraphAdapter = class {
233
282
  ) : selected;
234
283
  }
235
284
  const requestedRoot = repo || this.env.MINDGRAPH_REPO_ROOT || this.cwd;
236
- const requestedPath = await (0, import_promises.realpath)((0, import_node_path.resolve)(this.cwd, requestedRoot)).catch(
237
- () => (0, import_node_path.resolve)(this.cwd, requestedRoot)
285
+ const requestedPath = await (0, import_promises.realpath)((0, import_node_path2.resolve)(this.cwd, requestedRoot)).catch(
286
+ () => (0, import_node_path2.resolve)(this.cwd, requestedRoot)
238
287
  );
239
288
  const repoRoot = await this.gitRoot(requestedPath) ?? requestedPath;
240
289
  return this.resolveRepositoryIdentity(
@@ -255,7 +304,7 @@ var CodegraphAdapter = class {
255
304
  );
256
305
  }
257
306
  async resolveRepositoryIdentity(root, explicitRepoId, spaceUid) {
258
- const repoRoot = await (0, import_promises.realpath)(root).catch(() => (0, import_node_path.resolve)(root));
307
+ const repoRoot = await (0, import_promises.realpath)(root).catch(() => (0, import_node_path2.resolve)(root));
259
308
  const cacheKey = `${repoRoot}\0${explicitRepoId ?? ""}\0${spaceUid ?? ""}`;
260
309
  const cached = this.identityCache.get(cacheKey);
261
310
  if (cached) return cached;
@@ -285,20 +334,20 @@ var CodegraphAdapter = class {
285
334
  this.env
286
335
  ).catch(() => "");
287
336
  if (!rawCommonDir.trim()) {
288
- return `local:${(0, import_node_path.basename)(repoRoot)}`;
337
+ return `local:${(0, import_node_path2.basename)(repoRoot)}`;
289
338
  }
290
- const commonDir = (0, import_node_path.resolve)(repoRoot, rawCommonDir.trim());
291
- const identityDir = (0, import_node_path.join)(commonDir, "mindgraph");
292
- const identityFile = (0, import_node_path.join)(identityDir, "repository-id");
339
+ const commonDir = (0, import_node_path2.resolve)(repoRoot, rawCommonDir.trim());
340
+ const identityDir = (0, import_node_path2.join)(commonDir, "mindgraph");
341
+ const identityFile = (0, import_node_path2.join)(identityDir, "repository-id");
293
342
  try {
294
- const existing = (0, import_node_fs.readFileSync)(identityFile, "utf8").trim();
343
+ const existing = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
295
344
  if (existing) return existing;
296
345
  } catch {
297
346
  }
298
- (0, import_node_fs.mkdirSync)(identityDir, { recursive: true, mode: 448 });
299
- const generated = (0, import_node_crypto.randomUUID)();
347
+ (0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
348
+ const generated = (0, import_node_crypto2.randomUUID)();
300
349
  try {
301
- (0, import_node_fs.writeFileSync)(identityFile, `${generated}
350
+ (0, import_node_fs2.writeFileSync)(identityFile, `${generated}
302
351
  `, {
303
352
  encoding: "utf8",
304
353
  flag: "wx",
@@ -307,7 +356,7 @@ var CodegraphAdapter = class {
307
356
  return generated;
308
357
  } catch (cause) {
309
358
  if (cause.code !== "EEXIST") throw cause;
310
- const winner = (0, import_node_fs.readFileSync)(identityFile, "utf8").trim();
359
+ const winner = (0, import_node_fs2.readFileSync)(identityFile, "utf8").trim();
311
360
  if (!winner) throw new Error(`empty repository identity file: ${identityFile}`);
312
361
  return winner;
313
362
  }
@@ -320,7 +369,7 @@ var CodegraphAdapter = class {
320
369
  Math.min(this.timeoutMs, 2e3),
321
370
  this.env
322
371
  ).catch(() => "");
323
- return raw.trim() ? (0, import_node_path.resolve)(start, raw.trim()) : void 0;
372
+ return raw.trim() ? (0, import_node_path2.resolve)(start, raw.trim()) : void 0;
324
373
  }
325
374
  async availability(repository) {
326
375
  const cached = this.statusCache.get(repository.repoRoot);
@@ -437,7 +486,7 @@ var CodegraphAdapter = class {
437
486
  return this.resolveRepository(ref.repo || defaultRepo, fallbackSpaceUid);
438
487
  }
439
488
  if (ref.path) {
440
- const requested = (0, import_node_path.isAbsolute)(ref.path) ? (0, import_node_path.resolve)(ref.path) : (0, import_node_path.resolve)(this.cwd, ref.path);
489
+ const requested = (0, import_node_path2.isAbsolute)(ref.path) ? (0, import_node_path2.resolve)(ref.path) : (0, import_node_path2.resolve)(this.cwd, ref.path);
441
490
  const absolute = await (0, import_promises.realpath)(requested).catch(() => requested);
442
491
  const configured = await this.resolveRepositories(fallbackSpaceUid);
443
492
  const defining = [...configured].sort((a, b) => b.repoRoot.length - a.repoRoot.length).find((candidate) => pathContains(candidate.repoRoot, absolute));
@@ -514,19 +563,19 @@ var CodegraphAdapter = class {
514
563
  }
515
564
  configuredRepositories() {
516
565
  const configured = [];
517
- if (this.workspaceFile && (0, import_node_fs.existsSync)(this.workspaceFile)) {
566
+ if (this.workspaceFile && (0, import_node_fs2.existsSync)(this.workspaceFile)) {
518
567
  try {
519
568
  const parsed = JSON.parse(
520
- (0, import_node_fs.readFileSync)(this.workspaceFile, "utf8")
569
+ (0, import_node_fs2.readFileSync)(this.workspaceFile, "utf8")
521
570
  );
522
571
  if (parsed.v === 1 && Array.isArray(parsed.repositories)) {
523
- const configDirectory = (0, import_node_path.dirname)(this.workspaceFile);
524
- const base = (0, import_node_path.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path.dirname)(configDirectory) : configDirectory;
572
+ const configDirectory = (0, import_node_path2.dirname)(this.workspaceFile);
573
+ const base = (0, import_node_path2.basename)(configDirectory) === ".mindgraph" ? (0, import_node_path2.dirname)(configDirectory) : configDirectory;
525
574
  for (const repository of parsed.repositories) {
526
575
  if (!repository || typeof repository.root !== "string") continue;
527
576
  configured.push({
528
577
  repoId: typeof repository.repo_id === "string" ? repository.repo_id : void 0,
529
- repoRoot: (0, import_node_path.resolve)(base, repository.root),
578
+ repoRoot: (0, import_node_path2.resolve)(base, repository.root),
530
579
  spaceUid: typeof repository.space_uid === "string" ? repository.space_uid : void 0
531
580
  });
532
581
  }
@@ -541,7 +590,7 @@ var CodegraphAdapter = class {
541
590
  for (const [repoId, config] of Object.entries(parsed)) {
542
591
  configured.push({
543
592
  repoId,
544
- repoRoot: (0, import_node_path.resolve)(
593
+ repoRoot: (0, import_node_path2.resolve)(
545
594
  this.cwd,
546
595
  typeof config === "string" ? config : config.root
547
596
  ),
@@ -569,9 +618,9 @@ var CodegraphAdapter = class {
569
618
  const hits = await this.runJson(args);
570
619
  let candidates = hits.map(asNode).map(toCandidate).filter((item) => item !== null);
571
620
  if (ref.path) {
572
- const absolutePath = (0, import_node_path.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
621
+ const absolutePath = (0, import_node_path2.isAbsolute)(ref.path) ? await (0, import_promises.realpath)(ref.path).catch(() => ref.path) : void 0;
573
622
  const wanted = normalizePath(
574
- absolutePath ? (0, import_node_path.relative)(repository.repoRoot, absolutePath) : ref.path
623
+ absolutePath ? (0, import_node_path2.relative)(repository.repoRoot, absolutePath) : ref.path
575
624
  );
576
625
  candidates = candidates.filter((candidate) => candidate.path === wanted);
577
626
  }
@@ -672,7 +721,7 @@ var CodegraphAdapter = class {
672
721
  const candidates = [
673
722
  this.env.MINDGRAPH_CODEGRAPH_BIN,
674
723
  "codegraph",
675
- (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".local", "bin", "codegraph")
724
+ (0, import_node_path2.resolve)((0, import_node_os2.homedir)(), ".local", "bin", "codegraph")
676
725
  ].filter((candidate, index, all) => {
677
726
  return Boolean(candidate) && all.indexOf(candidate) === index;
678
727
  });
@@ -1219,9 +1268,9 @@ function primaryUids(value) {
1219
1268
  }
1220
1269
 
1221
1270
  // src/sync-tool.ts
1222
- var import_node_crypto2 = __toESM(require("crypto"));
1223
- var import_node_fs2 = __toESM(require("fs"));
1224
- var import_node_path2 = __toESM(require("path"));
1271
+ var import_node_crypto3 = __toESM(require("crypto"));
1272
+ var import_node_fs3 = __toESM(require("fs"));
1273
+ var import_node_path3 = __toESM(require("path"));
1225
1274
  var ok2 = (data) => ({
1226
1275
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1227
1276
  });
@@ -1298,25 +1347,25 @@ var SYNC_TOOL = {
1298
1347
  }
1299
1348
  };
1300
1349
  function safePath(root, candidate) {
1301
- const absolute = import_node_path2.default.resolve(root, candidate);
1302
- const relative2 = import_node_path2.default.relative(root, absolute);
1303
- if (relative2.startsWith("..") || import_node_path2.default.isAbsolute(relative2)) {
1350
+ const absolute = import_node_path3.default.resolve(root, candidate);
1351
+ const relative2 = import_node_path3.default.relative(root, absolute);
1352
+ if (relative2.startsWith("..") || import_node_path3.default.isAbsolute(relative2)) {
1304
1353
  throw new Error(`path escapes repository root: ${candidate}`);
1305
1354
  }
1306
1355
  return absolute;
1307
1356
  }
1308
1357
  function markdownFiles(target) {
1309
- if (!import_node_fs2.default.existsSync(target)) return [];
1310
- const stat = import_node_fs2.default.statSync(target);
1358
+ if (!import_node_fs3.default.existsSync(target)) return [];
1359
+ const stat = import_node_fs3.default.statSync(target);
1311
1360
  if (stat.isFile()) return target.endsWith(".md") ? [target] : [];
1312
1361
  if (!stat.isDirectory()) return [];
1313
- return import_node_fs2.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path2.default.join(target, entry.name)));
1362
+ return import_node_fs3.default.readdirSync(target, { withFileTypes: true }).flatMap((entry) => markdownFiles(import_node_path3.default.join(target, entry.name)));
1314
1363
  }
1315
1364
  function fileDescriptor(root, absolute) {
1316
- const content = import_node_fs2.default.readFileSync(absolute, "utf8");
1365
+ const content = import_node_fs3.default.readFileSync(absolute, "utf8");
1317
1366
  return {
1318
- logical_path: import_node_path2.default.relative(root, absolute).split(import_node_path2.default.sep).join("/"),
1319
- content_hash: import_node_crypto2.default.createHash("sha256").update(content).digest("hex"),
1367
+ logical_path: import_node_path3.default.relative(root, absolute).split(import_node_path3.default.sep).join("/"),
1368
+ content_hash: import_node_crypto3.default.createHash("sha256").update(content).digest("hex"),
1320
1369
  bytes: Buffer.byteLength(content),
1321
1370
  content
1322
1371
  };
@@ -1381,7 +1430,7 @@ async function handleSyncTool(client2, args) {
1381
1430
  }
1382
1431
  if (action === "status" && request.logical_path && request.file_present !== false) {
1383
1432
  const absolute = safePath(root, String(request.logical_path));
1384
- request.file_present = import_node_fs2.default.existsSync(absolute);
1433
+ request.file_present = import_node_fs3.default.existsSync(absolute);
1385
1434
  if (request.file_present && !request.content_hash) {
1386
1435
  request.content_hash = fileDescriptor(root, absolute).content_hash;
1387
1436
  }
@@ -1425,6 +1474,355 @@ async function scanRepository(remote, repository, requested, provider, agentId)
1425
1474
  };
1426
1475
  }
1427
1476
 
1477
+ // src/render.ts
1478
+ var LABEL_MAX = 200;
1479
+ var SUMMARY_MAX = 400;
1480
+ var QUOTE_MAX = 180;
1481
+ var ARTICLE_MAX = 6e3;
1482
+ var CHUNK_MAX = 6e3;
1483
+ var RELATIONSHIP_MAX = 40;
1484
+ var RENDER_CHAR_BUDGET = 24e3;
1485
+ var CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
1486
+ var INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
1487
+ function obj(value) {
1488
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1489
+ }
1490
+ function str(value) {
1491
+ return typeof value === "string" && value ? value : void 0;
1492
+ }
1493
+ function num(value) {
1494
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1495
+ }
1496
+ function clip(value, max, state) {
1497
+ const text = str(value);
1498
+ if (!text) return void 0;
1499
+ if (text.length <= max) return text;
1500
+ state.bounded = true;
1501
+ return `${text.slice(0, max - 1)}\u2026`;
1502
+ }
1503
+ function isNodeShaped(value) {
1504
+ const node = obj(value);
1505
+ return Boolean(node && str(node.uid) && (str(node.label) || str(node.node_type)));
1506
+ }
1507
+ function unwrapSearchResult(value) {
1508
+ if (isNodeShaped(value)) return value;
1509
+ const wrapper = obj(value);
1510
+ const inner = obj(wrapper?.node);
1511
+ if (inner && isNodeShaped(inner)) {
1512
+ const score = num(wrapper.score);
1513
+ return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
1514
+ }
1515
+ return void 0;
1516
+ }
1517
+ function statusTag(node) {
1518
+ const tags = [];
1519
+ if (node.superseded === true) {
1520
+ tags.push(
1521
+ node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
1522
+ );
1523
+ }
1524
+ const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
1525
+ if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
1526
+ if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
1527
+ if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
1528
+ return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
1529
+ }
1530
+ function firstQuote(node, state) {
1531
+ const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
1532
+ for (const raw of chunks) {
1533
+ const chunk = obj(raw);
1534
+ const quote = str(chunk?.quote) ?? str(obj(chunk?.anchor)?.exact);
1535
+ if (quote) return clip(quote, QUOTE_MAX, state);
1536
+ }
1537
+ return void 0;
1538
+ }
1539
+ function sourceTag(node) {
1540
+ const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
1541
+ const first = obj(docs[0]);
1542
+ const title = str(first?.title);
1543
+ if (!title) return void 0;
1544
+ const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
1545
+ return `from: ${title}${extra}`;
1546
+ }
1547
+ function layer7Fields(node, state) {
1548
+ const fields = obj(obj(node.props)?.fields);
1549
+ if (!fields) return void 0;
1550
+ const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
1551
+ if (pairs.length === 0) return void 0;
1552
+ return clip(pairs.join("; "), SUMMARY_MAX, state);
1553
+ }
1554
+ function nodeSummaryText(node, state) {
1555
+ const props = obj(node.props);
1556
+ return clip(node.summary, SUMMARY_MAX, state) ?? clip(props?.content, SUMMARY_MAX, state) ?? clip(props?.description, SUMMARY_MAX, state) ?? clip(props?.definition, SUMMARY_MAX, state);
1557
+ }
1558
+ function nodeLines(node, state) {
1559
+ const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
1560
+ const type = str(node.node_type) || "Node";
1561
+ const confidence = num(node.confidence);
1562
+ const score = num(node.retrieval_score);
1563
+ const parenthetical = [
1564
+ type,
1565
+ confidence !== void 0 ? `confidence ${confidence}` : void 0,
1566
+ score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
1567
+ ].filter(Boolean).join(", ");
1568
+ const head = `- **${label}** [${str(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
1569
+ const summary = nodeSummaryText(node, state);
1570
+ const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
1571
+ const fields = layer7Fields(node, state);
1572
+ if (fields) lines.push(` - fields: ${fields}`);
1573
+ const quote = firstQuote(node, state);
1574
+ if (quote) lines.push(` - source quote: "${quote}"`);
1575
+ const source = sourceTag(node);
1576
+ if (source) lines.push(` - ${source}`);
1577
+ const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
1578
+ if (believers.length > 0) {
1579
+ const rendered = believers.slice(0, 3).map((raw) => {
1580
+ const believer = obj(raw);
1581
+ const conf = num(believer?.confidence);
1582
+ return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
1583
+ }).join(", ");
1584
+ lines.push(` - believed by: ${rendered}`);
1585
+ }
1586
+ return lines.join("\n");
1587
+ }
1588
+ function groupedNodeSections(nodes, state, headingPrefix = "") {
1589
+ const groups = /* @__PURE__ */ new Map();
1590
+ for (const node of nodes) {
1591
+ const type = str(node.node_type) || "Node";
1592
+ const group = groups.get(type) ?? [];
1593
+ group.push(node);
1594
+ groups.set(type, group);
1595
+ }
1596
+ const sections = [];
1597
+ let first = true;
1598
+ for (const [type, group] of groups) {
1599
+ sections.push({
1600
+ heading: `${first ? headingPrefix : ""}### ${type}`,
1601
+ items: group.map((node) => nodeLines(node, state))
1602
+ });
1603
+ first = false;
1604
+ }
1605
+ return sections;
1606
+ }
1607
+ function relationshipItems(edges, labelByUid, state) {
1608
+ const lines = [];
1609
+ for (const raw of edges) {
1610
+ const edge = obj(raw);
1611
+ if (!edge) continue;
1612
+ const type = str(edge.edge_type) || "?";
1613
+ const from = str(edge.from_uid);
1614
+ const to = str(edge.to_uid);
1615
+ if (CURATION_EDGE_TYPES.has(type)) continue;
1616
+ if (!from || !to || from === to) continue;
1617
+ const fromLabel = labelByUid.get(from);
1618
+ const toLabel = labelByUid.get(to);
1619
+ if (!fromLabel || !toLabel) continue;
1620
+ lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
1621
+ }
1622
+ if (lines.length > RELATIONSHIP_MAX) {
1623
+ state.bounded = true;
1624
+ const extra = lines.length - RELATIONSHIP_MAX;
1625
+ return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
1626
+ }
1627
+ return lines;
1628
+ }
1629
+ function assemble(header, sections, state) {
1630
+ const lines = [header];
1631
+ let used = header.length;
1632
+ for (const section of sections) {
1633
+ if (section.items.length === 0 && !section.heading) continue;
1634
+ const headingCost = section.heading.length + 2;
1635
+ if (used + headingCost > RENDER_CHAR_BUDGET) {
1636
+ state.bounded = true;
1637
+ continue;
1638
+ }
1639
+ let sectionUsed = 0;
1640
+ const kept = [];
1641
+ let dropped = 0;
1642
+ for (const item of section.items) {
1643
+ if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
1644
+ dropped += 1;
1645
+ continue;
1646
+ }
1647
+ kept.push(item);
1648
+ sectionUsed += item.length + 1;
1649
+ }
1650
+ if (kept.length === 0 && section.items.length > 0) {
1651
+ state.bounded = true;
1652
+ continue;
1653
+ }
1654
+ if (dropped > 0) {
1655
+ state.bounded = true;
1656
+ const marker = `(+${dropped} more not shown)`;
1657
+ kept.push(marker);
1658
+ sectionUsed += marker.length + 1;
1659
+ }
1660
+ lines.push(
1661
+ section.items.length === 0 ? section.heading : `${section.heading}
1662
+ ${kept.join("\n")}`
1663
+ );
1664
+ used += headingCost + sectionUsed;
1665
+ }
1666
+ if (state.bounded) {
1667
+ lines.push(
1668
+ '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
1669
+ );
1670
+ }
1671
+ return lines.join("\n\n");
1672
+ }
1673
+ function policySections(payload, state) {
1674
+ const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
1675
+ if (policies.length === 0) return [];
1676
+ const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
1677
+ const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
1678
+ return `- ${str(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
1679
+ });
1680
+ return [{ heading: "## Applicable policies", items }];
1681
+ }
1682
+ function hiddenSections(payload) {
1683
+ const hidden = payload.hidden_item_count;
1684
+ if (hidden === void 0 || hidden === null) return [];
1685
+ return [
1686
+ {
1687
+ heading: `Note: ${hidden} item(s) were hidden by access scoping.`,
1688
+ items: []
1689
+ }
1690
+ ];
1691
+ }
1692
+ function renderContext(response) {
1693
+ const payload = obj(response);
1694
+ if (!payload) return void 0;
1695
+ const graph = obj(payload.graph);
1696
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
1697
+ const shaped = rawNodes.filter(isNodeShaped);
1698
+ const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str(node.node_type) || ""));
1699
+ const infraOnly = shaped.length - nodes.length;
1700
+ const articles = Array.isArray(payload.articles) ? payload.articles : [];
1701
+ const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
1702
+ const state = { bounded: false };
1703
+ const sections = [];
1704
+ if (articles.length > 0) {
1705
+ sections.push({
1706
+ heading: "## Knowledge articles",
1707
+ items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
1708
+ (article) => `### ${str(article.label) || "(untitled)"} [${str(article.uid) || "?"}]
1709
+ ${clip(article.content, ARTICLE_MAX, state) || ""}`
1710
+ )
1711
+ });
1712
+ }
1713
+ if (nodes.length > 0) {
1714
+ sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
1715
+ const labelByUid = /* @__PURE__ */ new Map();
1716
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
1717
+ const relationships = relationshipItems(
1718
+ Array.isArray(graph?.edges) ? graph.edges : [],
1719
+ labelByUid,
1720
+ state
1721
+ );
1722
+ if (relationships.length > 0) {
1723
+ sections.push({ heading: "## Relationships", items: relationships });
1724
+ }
1725
+ } else if (infraOnly > 0) {
1726
+ sections.push({
1727
+ heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
1728
+ items: []
1729
+ });
1730
+ }
1731
+ if (chunks.length > 0) {
1732
+ sections.push({
1733
+ heading: "## Source excerpts",
1734
+ items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
1735
+ (chunk) => `- [${str(chunk.chunk_uid) || "?"}] from "${str(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
1736
+ )
1737
+ });
1738
+ }
1739
+ sections.push(...policySections(payload, state));
1740
+ sections.push(...hiddenSections(payload));
1741
+ if (sections.length === 0) {
1742
+ return "No results found for this search.";
1743
+ }
1744
+ if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
1745
+ return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
1746
+ }
1747
+ return assemble("# Retrieved context", sections, state);
1748
+ }
1749
+ function renderNodeList(response, title, limit) {
1750
+ let candidates = [];
1751
+ if (Array.isArray(response)) {
1752
+ candidates = response;
1753
+ } else {
1754
+ const payload = obj(response);
1755
+ if (!payload) return void 0;
1756
+ for (const key of ["nodes", "results", "items", "goals", "questions", "claims", "preferences", "documents"]) {
1757
+ const candidate = payload[key];
1758
+ if (Array.isArray(candidate) && candidate.some((entry) => unwrapSearchResult(entry))) {
1759
+ candidates = candidate;
1760
+ break;
1761
+ }
1762
+ }
1763
+ }
1764
+ let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
1765
+ if (nodes.length === 0) return void 0;
1766
+ const total = nodes.length;
1767
+ const state = { bounded: false };
1768
+ if (limit !== void 0 && limit > 0 && nodes.length > limit) {
1769
+ nodes = nodes.slice(0, limit);
1770
+ state.bounded = true;
1771
+ }
1772
+ const sections = groupedNodeSections(nodes, state);
1773
+ const shown = nodes.length;
1774
+ return assemble(
1775
+ `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
1776
+ sections,
1777
+ state
1778
+ );
1779
+ }
1780
+ function renderTraversal(response) {
1781
+ const payload = obj(response);
1782
+ if (!payload) return void 0;
1783
+ const container = obj(payload.graph) ?? payload;
1784
+ const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
1785
+ const nodes = rawNodes.filter(isNodeShaped);
1786
+ const paths = Array.isArray(payload.paths) ? payload.paths : [];
1787
+ if (nodes.length === 0 && paths.length === 0) return void 0;
1788
+ const state = { bounded: false };
1789
+ const sections = [];
1790
+ const labelByUid = /* @__PURE__ */ new Map();
1791
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
1792
+ if (paths.length > 0) {
1793
+ const items = paths.map((raw) => obj(raw)).filter((path3) => Boolean(path3)).map((path3, index) => {
1794
+ const uids = Array.isArray(path3.node_uids) ? path3.node_uids.map(String) : [];
1795
+ const labels = Array.isArray(path3.labels) ? path3.labels.map(String) : Array.isArray(path3.nodes) ? path3.nodes.map((hop) => {
1796
+ const hopNode = obj(hop);
1797
+ return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
1798
+ }) : [];
1799
+ const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
1800
+ const cost = num(path3.cost) ?? num(path3.path_cost);
1801
+ const confidence = num(path3.path_confidence);
1802
+ const meta = [
1803
+ cost !== void 0 ? `cost ${cost}` : void 0,
1804
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
1805
+ ].filter(Boolean);
1806
+ const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
1807
+ return labels.length > 0 && uids.length > 0 ? `${head}
1808
+ uids: ${uids.join(" \u2192 ")}` : head;
1809
+ });
1810
+ sections.push({ heading: "## Paths", items });
1811
+ }
1812
+ if (nodes.length > 0) {
1813
+ sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
1814
+ const relationships = relationshipItems(
1815
+ Array.isArray(container.edges) ? container.edges : [],
1816
+ labelByUid,
1817
+ state
1818
+ );
1819
+ if (relationships.length > 0) {
1820
+ sections.push({ heading: "## Relationships", items: relationships });
1821
+ }
1822
+ }
1823
+ return assemble("# Graph traversal", sections, state);
1824
+ }
1825
+
1428
1826
  // src/tools.ts
1429
1827
  var TOOLS = [
1430
1828
  {
@@ -1999,10 +2397,16 @@ var TOOLS = [
1999
2397
  "neighborhood",
2000
2398
  "path",
2001
2399
  "subgraph",
2400
+ "top_k_paths",
2002
2401
  "document_index"
2003
2402
  ],
2004
2403
  description: "Retrieval or traversal strategy"
2005
2404
  },
2405
+ format: {
2406
+ type: "string",
2407
+ enum: ["text", "json"],
2408
+ description: "Output format (default 'text'): a compact rendered context block \u2014 labels, uids, summaries, status tags, relationships. Pass 'json' for the raw server response (full props, source-chunk offsets, retrieval metadata)."
2409
+ },
2006
2410
  query: {
2007
2411
  type: "string",
2008
2412
  description: "Search query \u2014 for 'text'/'context': 1-3 BM25 keywords, drop filler words. For 'semantic'/'hybrid': natural language is OK since it uses vector similarity."
@@ -2062,7 +2466,35 @@ var TOOLS = [
2062
2466
  },
2063
2467
  include_documents: {
2064
2468
  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."
2469
+ 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 \u2014 or false in any profile to drop the article leg."
2470
+ },
2471
+ article_limit: {
2472
+ type: "number",
2473
+ description: "Max wiki articles in context results (default 3; 0 disables the article leg). An explicit value overrides profile defaults, including the coding profile's articles-off default."
2474
+ },
2475
+ valid_at: {
2476
+ type: "string",
2477
+ description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
2478
+ },
2479
+ created_after: {
2480
+ type: "number",
2481
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
2482
+ },
2483
+ created_before: {
2484
+ type: "number",
2485
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested before this time"
2486
+ },
2487
+ explain: {
2488
+ type: "boolean",
2489
+ description: "hybrid action: include per-leg retrieval scoring (legs) in the response"
2490
+ },
2491
+ k: {
2492
+ type: "number",
2493
+ description: "top_k_paths action: number of cheapest paths (default 3, cap 25)"
2494
+ },
2495
+ max_cost: {
2496
+ type: "number",
2497
+ description: "top_k_paths action: optional cost ceiling for returned paths"
2066
2498
  },
2067
2499
  include_chunks: {
2068
2500
  type: "boolean",
@@ -2441,6 +2873,18 @@ async function handleTool(client2, name, args) {
2441
2873
  ].includes(action);
2442
2874
  return attach ? await attachCodeRefsToToolResult(client2, result, args) : result;
2443
2875
  } catch (e) {
2876
+ const lifted = conflictState(e);
2877
+ if (Object.keys(lifted).length > 0) {
2878
+ return {
2879
+ content: [
2880
+ {
2881
+ type: "text",
2882
+ text: JSON.stringify({ error: errorDetail(e), ...lifted })
2883
+ }
2884
+ ],
2885
+ isError: true
2886
+ };
2887
+ }
2444
2888
  return err3(errorDetail(e));
2445
2889
  }
2446
2890
  }
@@ -2956,6 +3400,7 @@ async function handlePlan(client2, args) {
2956
3400
  async function handleRetrieve(client2, args) {
2957
3401
  const {
2958
3402
  action,
3403
+ format,
2959
3404
  query,
2960
3405
  start_uid,
2961
3406
  end_uid,
@@ -2976,12 +3421,24 @@ async function handleRetrieve(client2, args) {
2976
3421
  graph_max_depth,
2977
3422
  confidence_min,
2978
3423
  salience_min,
3424
+ article_limit,
3425
+ valid_at,
3426
+ created_after,
3427
+ created_before,
3428
+ explain,
3429
+ k,
3430
+ max_cost,
2979
3431
  agent_id
2980
3432
  } = args;
3433
+ const rendered = (raw, render) => {
3434
+ if (format === "json") return ok3(raw);
3435
+ const text = render(raw);
3436
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3437
+ };
2981
3438
  switch (action) {
2982
3439
  case "context":
2983
3440
  if (!query) return err3("query is required for context retrieval");
2984
- return ok3(
3441
+ return rendered(
2985
3442
  await client2.retrieveContext({
2986
3443
  query,
2987
3444
  node_limit: limit,
@@ -2990,110 +3447,139 @@ async function handleRetrieve(client2, args) {
2990
3447
  chunk_limit: include_chunks ? limit ?? 5 : 0,
2991
3448
  // Scoped by default, never walled: the coding profile drops the
2992
3449
  // document/article leg (the "Bob Work" class of noise) unless the
2993
- // model opts in for a genuinely document-shaped question.
2994
- ...process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
3450
+ // model opts in for a genuinely document-shaped question. Any
3451
+ // profile can drop it with include_documents: false (previously a
3452
+ // silent no-op outside coding). An EXPLICIT article_limit is at
3453
+ // least as clear an opt-in as include_documents:true — it always
3454
+ // wins over profile defaults (a coding-profile model reaching for
3455
+ // the advertised knob must not get a silent no-op).
3456
+ ...article_limit !== void 0 ? { article_limit } : include_documents === false || process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
3457
+ valid_at,
2995
3458
  include_graph,
2996
3459
  graph_expansion_limit: graph_expansion_limit ?? 3,
2997
3460
  graph_max_depth: graph_max_depth ?? 2
2998
- })
3461
+ }),
3462
+ renderContext
2999
3463
  );
3000
3464
  case "text":
3001
3465
  if (!query) return err3("query is required for text search");
3002
- return ok3(
3466
+ return rendered(
3003
3467
  await client2.retrieve({
3004
3468
  action: "text",
3005
3469
  query,
3006
3470
  limit,
3007
3471
  node_types,
3008
3472
  layer,
3473
+ explain,
3009
3474
  agent_id
3010
- })
3475
+ }),
3476
+ (raw) => renderNodeList(raw, `Text search: ${query}`)
3011
3477
  );
3012
3478
  case "semantic":
3013
3479
  if (!query) return err3("query is required for semantic search");
3014
- return ok3(
3480
+ return rendered(
3015
3481
  await client2.retrieve({
3016
3482
  action: "semantic",
3017
3483
  query,
3018
3484
  k: limit,
3019
3485
  node_types,
3020
3486
  layer,
3487
+ explain,
3021
3488
  agent_id
3022
- })
3489
+ }),
3490
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`)
3023
3491
  );
3024
3492
  case "hybrid":
3025
3493
  if (!query) return err3("query is required for hybrid search");
3026
- return ok3(
3494
+ return rendered(
3027
3495
  await client2.retrieve({
3028
3496
  action: "hybrid",
3029
3497
  query,
3030
3498
  k: limit,
3031
3499
  node_types,
3032
3500
  layer,
3501
+ explain,
3033
3502
  agent_id
3034
- })
3503
+ }),
3504
+ // With explain, per-leg scoring must survive — raw JSON only.
3505
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
3035
3506
  );
3036
- // Structured queries
3507
+ // Structured queries. These go through the generic /retrieve action
3508
+ // dispatch rather than the SDK's no-argument convenience methods — the
3509
+ // conveniences silently dropped `limit`/`offset` (the tool advertised
3510
+ // them; five actions ignored them).
3037
3511
  case "active_goals":
3038
- return ok3(await client2.getGoals());
3039
3512
  case "open_questions":
3040
- return ok3(await client2.getOpenQuestions());
3041
3513
  case "weak_claims":
3042
- return ok3(await client2.getWeakClaims());
3043
3514
  case "pending_approvals":
3044
- return ok3(await client2.getPendingApprovals());
3045
3515
  case "unresolved_contradictions":
3046
- return ok3(await client2.getContradictions());
3516
+ return rendered(
3517
+ await client2.retrieve({
3518
+ action,
3519
+ limit,
3520
+ offset,
3521
+ agent_id
3522
+ }),
3523
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
3524
+ );
3047
3525
  case "stale_derivations":
3048
- return ok3(
3526
+ return rendered(
3049
3527
  await client2.retrieve({
3050
3528
  action: "stale_derivations",
3051
3529
  limit,
3052
3530
  offset
3053
- })
3531
+ }),
3532
+ (raw) => renderNodeList(raw, "stale derivations", limit)
3054
3533
  );
3055
3534
  case "preferences":
3056
- return ok3(
3535
+ return rendered(
3057
3536
  await client2.retrieve({
3058
3537
  action: "preferences",
3059
3538
  ...query ? { query, k: limit } : {},
3060
3539
  limit,
3061
3540
  agent_id
3062
- })
3541
+ }),
3542
+ (raw) => renderNodeList(raw, "Preferences", limit)
3063
3543
  );
3064
3544
  case "layer":
3065
3545
  if (!layer) return err3("layer is required for layer retrieval");
3066
- return ok3(
3546
+ return rendered(
3067
3547
  await client2.retrieve({
3068
3548
  action: "layer",
3069
3549
  layer,
3070
3550
  limit,
3551
+ node_types,
3071
3552
  agent_id
3072
- })
3553
+ }),
3554
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
3073
3555
  );
3074
3556
  case "recent":
3075
- return ok3(
3557
+ return rendered(
3076
3558
  await client2.retrieve({
3077
3559
  action: "recent",
3078
3560
  limit,
3079
3561
  node_types,
3080
3562
  confidence_min,
3081
3563
  salience_min,
3564
+ created_after,
3565
+ created_before,
3082
3566
  agent_id
3083
- })
3567
+ }),
3568
+ (raw) => renderNodeList(raw, "Recently ingested", limit)
3084
3569
  );
3085
3570
  // Document inventory
3086
3571
  case "document_index":
3087
- return ok3(
3572
+ return rendered(
3088
3573
  await client2.getNodes({
3089
3574
  node_type: "Document",
3090
3575
  limit: limit ?? 100
3091
- })
3576
+ }),
3577
+ (raw) => renderNodeList(raw, "Ingested documents")
3092
3578
  );
3093
3579
  // Graph traversal
3094
3580
  case "chain":
3095
3581
  if (!start_uid) return err3("start_uid is required for chain traversal");
3096
- return ok3(
3582
+ return rendered(
3097
3583
  await client2.traverse({
3098
3584
  action: "chain",
3099
3585
  start_uid,
@@ -3101,11 +3587,12 @@ async function handleRetrieve(client2, args) {
3101
3587
  exclude_edge_types,
3102
3588
  include_provenance,
3103
3589
  max_nodes
3104
- })
3590
+ }),
3591
+ renderTraversal
3105
3592
  );
3106
3593
  case "neighborhood":
3107
3594
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3108
- return ok3(
3595
+ return rendered(
3109
3596
  await client2.traverse({
3110
3597
  action: "neighborhood",
3111
3598
  start_uid,
@@ -3115,12 +3602,13 @@ async function handleRetrieve(client2, args) {
3115
3602
  exclude_edge_types,
3116
3603
  include_provenance,
3117
3604
  max_nodes
3118
- })
3605
+ }),
3606
+ renderTraversal
3119
3607
  );
3120
3608
  case "path":
3121
3609
  if (!start_uid) return err3("start_uid is required for path traversal");
3122
3610
  if (!end_uid) return err3("end_uid is required for path traversal");
3123
- return ok3(
3611
+ return rendered(
3124
3612
  await client2.traverse({
3125
3613
  action: "path",
3126
3614
  start_uid,
@@ -3131,11 +3619,12 @@ async function handleRetrieve(client2, args) {
3131
3619
  exclude_edge_types,
3132
3620
  include_provenance,
3133
3621
  max_nodes
3134
- })
3622
+ }),
3623
+ renderTraversal
3135
3624
  );
3136
3625
  case "subgraph":
3137
3626
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
3138
- return ok3(
3627
+ return rendered(
3139
3628
  await client2.traverse({
3140
3629
  action: "subgraph",
3141
3630
  start_uid,
@@ -3145,7 +3634,24 @@ async function handleRetrieve(client2, args) {
3145
3634
  exclude_edge_types,
3146
3635
  include_provenance,
3147
3636
  max_nodes
3148
- })
3637
+ }),
3638
+ renderTraversal
3639
+ );
3640
+ case "top_k_paths":
3641
+ if (!start_uid) return err3("start_uid is required for top_k_paths");
3642
+ if (!end_uid) return err3("end_uid is required for top_k_paths");
3643
+ return rendered(
3644
+ await client2.traverse({
3645
+ action: "top_k_paths",
3646
+ start_uid,
3647
+ end_uid,
3648
+ k,
3649
+ max_hops: max_depth,
3650
+ max_cost,
3651
+ edge_types,
3652
+ exclude_edge_types
3653
+ }),
3654
+ renderTraversal
3149
3655
  );
3150
3656
  default:
3151
3657
  return err3(`Unknown retrieve action: ${action}`);
@@ -3876,7 +4382,7 @@ async function readGovernedResource(client2, uri, governanceConfig) {
3876
4382
  // src/index.ts
3877
4383
  var API_KEY = process.env.MINDGRAPH_API_KEY;
3878
4384
  var BASE_URL = process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud";
3879
- var AGENT_ID = process.env.MINDGRAPH_AGENT_ID || "mcp";
4385
+ var AGENT_ID = process.env.MINDGRAPH_AGENT_ID || loadHookEnv().agentId || stableAgentId();
3880
4386
  var ORG_ID = process.env.MINDGRAPH_ORG_ID;
3881
4387
  var PROFILE = process.env.MINDGRAPH_PROFILE || "general";
3882
4388
  var HARNESS = process.env.MINDGRAPH_HARNESS || "generic";
@@ -3998,7 +4504,7 @@ if (PROFILE === "coding") {
3998
4504
  var server = new import_server.Server(
3999
4505
  {
4000
4506
  name: "mindgraph",
4001
- version: "0.14.9"
4507
+ version: "0.16.0"
4002
4508
  },
4003
4509
  {
4004
4510
  capabilities: {
@@ -4212,7 +4718,7 @@ async function main() {
4212
4718
  const transport = new import_stdio.StdioServerTransport();
4213
4719
  await server.connect(transport);
4214
4720
  console.error(
4215
- `MindGraph MCP server v0.7.1 running on stdio (${BASE_URL})`
4721
+ `MindGraph MCP server v${"0.16.0"} running on stdio (${BASE_URL})`
4216
4722
  );
4217
4723
  }
4218
4724
  main().catch((error) => {