mindgraph-mcp 0.15.0 → 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.
package/dist/cli.js CHANGED
@@ -2412,6 +2412,361 @@ var init_sync_tool = __esm({
2412
2412
  }
2413
2413
  });
2414
2414
 
2415
+ // src/render.ts
2416
+ function obj(value) {
2417
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2418
+ }
2419
+ function str(value) {
2420
+ return typeof value === "string" && value ? value : void 0;
2421
+ }
2422
+ function num(value) {
2423
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2424
+ }
2425
+ function clip(value, max, state) {
2426
+ const text = str(value);
2427
+ if (!text) return void 0;
2428
+ if (text.length <= max) return text;
2429
+ state.bounded = true;
2430
+ return `${text.slice(0, max - 1)}\u2026`;
2431
+ }
2432
+ function isNodeShaped(value) {
2433
+ const node = obj(value);
2434
+ return Boolean(node && str(node.uid) && (str(node.label) || str(node.node_type)));
2435
+ }
2436
+ function unwrapSearchResult(value) {
2437
+ if (isNodeShaped(value)) return value;
2438
+ const wrapper = obj(value);
2439
+ const inner = obj(wrapper?.node);
2440
+ if (inner && isNodeShaped(inner)) {
2441
+ const score = num(wrapper.score);
2442
+ return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
2443
+ }
2444
+ return void 0;
2445
+ }
2446
+ function statusTag(node) {
2447
+ const tags = [];
2448
+ if (node.superseded === true) {
2449
+ tags.push(
2450
+ node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
2451
+ );
2452
+ }
2453
+ const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
2454
+ if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
2455
+ if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
2456
+ if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
2457
+ return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
2458
+ }
2459
+ function firstQuote(node, state) {
2460
+ const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
2461
+ for (const raw of chunks) {
2462
+ const chunk = obj(raw);
2463
+ const quote = str(chunk?.quote) ?? str(obj(chunk?.anchor)?.exact);
2464
+ if (quote) return clip(quote, QUOTE_MAX, state);
2465
+ }
2466
+ return void 0;
2467
+ }
2468
+ function sourceTag(node) {
2469
+ const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
2470
+ const first = obj(docs[0]);
2471
+ const title = str(first?.title);
2472
+ if (!title) return void 0;
2473
+ const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
2474
+ return `from: ${title}${extra}`;
2475
+ }
2476
+ function layer7Fields(node, state) {
2477
+ const fields = obj(obj(node.props)?.fields);
2478
+ if (!fields) return void 0;
2479
+ const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
2480
+ if (pairs.length === 0) return void 0;
2481
+ return clip(pairs.join("; "), SUMMARY_MAX, state);
2482
+ }
2483
+ function nodeSummaryText(node, state) {
2484
+ const props = obj(node.props);
2485
+ 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);
2486
+ }
2487
+ function nodeLines(node, state) {
2488
+ const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
2489
+ const type = str(node.node_type) || "Node";
2490
+ const confidence = num(node.confidence);
2491
+ const score = num(node.retrieval_score);
2492
+ const parenthetical = [
2493
+ type,
2494
+ confidence !== void 0 ? `confidence ${confidence}` : void 0,
2495
+ score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
2496
+ ].filter(Boolean).join(", ");
2497
+ const head = `- **${label}** [${str(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
2498
+ const summary = nodeSummaryText(node, state);
2499
+ const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
2500
+ const fields = layer7Fields(node, state);
2501
+ if (fields) lines.push(` - fields: ${fields}`);
2502
+ const quote = firstQuote(node, state);
2503
+ if (quote) lines.push(` - source quote: "${quote}"`);
2504
+ const source = sourceTag(node);
2505
+ if (source) lines.push(` - ${source}`);
2506
+ const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
2507
+ if (believers.length > 0) {
2508
+ const rendered = believers.slice(0, 3).map((raw) => {
2509
+ const believer = obj(raw);
2510
+ const conf = num(believer?.confidence);
2511
+ return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
2512
+ }).join(", ");
2513
+ lines.push(` - believed by: ${rendered}`);
2514
+ }
2515
+ return lines.join("\n");
2516
+ }
2517
+ function groupedNodeSections(nodes, state, headingPrefix = "") {
2518
+ const groups = /* @__PURE__ */ new Map();
2519
+ for (const node of nodes) {
2520
+ const type = str(node.node_type) || "Node";
2521
+ const group = groups.get(type) ?? [];
2522
+ group.push(node);
2523
+ groups.set(type, group);
2524
+ }
2525
+ const sections = [];
2526
+ let first = true;
2527
+ for (const [type, group] of groups) {
2528
+ sections.push({
2529
+ heading: `${first ? headingPrefix : ""}### ${type}`,
2530
+ items: group.map((node) => nodeLines(node, state))
2531
+ });
2532
+ first = false;
2533
+ }
2534
+ return sections;
2535
+ }
2536
+ function relationshipItems(edges, labelByUid, state) {
2537
+ const lines = [];
2538
+ for (const raw of edges) {
2539
+ const edge = obj(raw);
2540
+ if (!edge) continue;
2541
+ const type = str(edge.edge_type) || "?";
2542
+ const from = str(edge.from_uid);
2543
+ const to = str(edge.to_uid);
2544
+ if (CURATION_EDGE_TYPES.has(type)) continue;
2545
+ if (!from || !to || from === to) continue;
2546
+ const fromLabel = labelByUid.get(from);
2547
+ const toLabel = labelByUid.get(to);
2548
+ if (!fromLabel || !toLabel) continue;
2549
+ lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
2550
+ }
2551
+ if (lines.length > RELATIONSHIP_MAX) {
2552
+ state.bounded = true;
2553
+ const extra = lines.length - RELATIONSHIP_MAX;
2554
+ return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
2555
+ }
2556
+ return lines;
2557
+ }
2558
+ function assemble(header, sections, state) {
2559
+ const lines = [header];
2560
+ let used = header.length;
2561
+ for (const section of sections) {
2562
+ if (section.items.length === 0 && !section.heading) continue;
2563
+ const headingCost = section.heading.length + 2;
2564
+ if (used + headingCost > RENDER_CHAR_BUDGET) {
2565
+ state.bounded = true;
2566
+ continue;
2567
+ }
2568
+ let sectionUsed = 0;
2569
+ const kept = [];
2570
+ let dropped = 0;
2571
+ for (const item of section.items) {
2572
+ if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
2573
+ dropped += 1;
2574
+ continue;
2575
+ }
2576
+ kept.push(item);
2577
+ sectionUsed += item.length + 1;
2578
+ }
2579
+ if (kept.length === 0 && section.items.length > 0) {
2580
+ state.bounded = true;
2581
+ continue;
2582
+ }
2583
+ if (dropped > 0) {
2584
+ state.bounded = true;
2585
+ const marker = `(+${dropped} more not shown)`;
2586
+ kept.push(marker);
2587
+ sectionUsed += marker.length + 1;
2588
+ }
2589
+ lines.push(
2590
+ section.items.length === 0 ? section.heading : `${section.heading}
2591
+ ${kept.join("\n")}`
2592
+ );
2593
+ used += headingCost + sectionUsed;
2594
+ }
2595
+ if (state.bounded) {
2596
+ lines.push(
2597
+ '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
2598
+ );
2599
+ }
2600
+ return lines.join("\n\n");
2601
+ }
2602
+ function policySections(payload, state) {
2603
+ const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
2604
+ if (policies.length === 0) return [];
2605
+ const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
2606
+ const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
2607
+ return `- ${str(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
2608
+ });
2609
+ return [{ heading: "## Applicable policies", items }];
2610
+ }
2611
+ function hiddenSections(payload) {
2612
+ const hidden = payload.hidden_item_count;
2613
+ if (hidden === void 0 || hidden === null) return [];
2614
+ return [
2615
+ {
2616
+ heading: `Note: ${hidden} item(s) were hidden by access scoping.`,
2617
+ items: []
2618
+ }
2619
+ ];
2620
+ }
2621
+ function renderContext(response) {
2622
+ const payload = obj(response);
2623
+ if (!payload) return void 0;
2624
+ const graph = obj(payload.graph);
2625
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
2626
+ const shaped = rawNodes.filter(isNodeShaped);
2627
+ const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str(node.node_type) || ""));
2628
+ const infraOnly = shaped.length - nodes.length;
2629
+ const articles = Array.isArray(payload.articles) ? payload.articles : [];
2630
+ const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
2631
+ const state = { bounded: false };
2632
+ const sections = [];
2633
+ if (articles.length > 0) {
2634
+ sections.push({
2635
+ heading: "## Knowledge articles",
2636
+ items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
2637
+ (article) => `### ${str(article.label) || "(untitled)"} [${str(article.uid) || "?"}]
2638
+ ${clip(article.content, ARTICLE_MAX, state) || ""}`
2639
+ )
2640
+ });
2641
+ }
2642
+ if (nodes.length > 0) {
2643
+ sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
2644
+ const labelByUid = /* @__PURE__ */ new Map();
2645
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
2646
+ const relationships = relationshipItems(
2647
+ Array.isArray(graph?.edges) ? graph.edges : [],
2648
+ labelByUid,
2649
+ state
2650
+ );
2651
+ if (relationships.length > 0) {
2652
+ sections.push({ heading: "## Relationships", items: relationships });
2653
+ }
2654
+ } else if (infraOnly > 0) {
2655
+ sections.push({
2656
+ heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
2657
+ items: []
2658
+ });
2659
+ }
2660
+ if (chunks.length > 0) {
2661
+ sections.push({
2662
+ heading: "## Source excerpts",
2663
+ items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
2664
+ (chunk) => `- [${str(chunk.chunk_uid) || "?"}] from "${str(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
2665
+ )
2666
+ });
2667
+ }
2668
+ sections.push(...policySections(payload, state));
2669
+ sections.push(...hiddenSections(payload));
2670
+ if (sections.length === 0) {
2671
+ return "No results found for this search.";
2672
+ }
2673
+ if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
2674
+ return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
2675
+ }
2676
+ return assemble("# Retrieved context", sections, state);
2677
+ }
2678
+ function renderNodeList(response, title, limit) {
2679
+ let candidates = [];
2680
+ if (Array.isArray(response)) {
2681
+ candidates = response;
2682
+ } else {
2683
+ const payload = obj(response);
2684
+ if (!payload) return void 0;
2685
+ for (const key of ["nodes", "results", "items", "goals", "questions", "claims", "preferences", "documents"]) {
2686
+ const candidate = payload[key];
2687
+ if (Array.isArray(candidate) && candidate.some((entry) => unwrapSearchResult(entry))) {
2688
+ candidates = candidate;
2689
+ break;
2690
+ }
2691
+ }
2692
+ }
2693
+ let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
2694
+ if (nodes.length === 0) return void 0;
2695
+ const total = nodes.length;
2696
+ const state = { bounded: false };
2697
+ if (limit !== void 0 && limit > 0 && nodes.length > limit) {
2698
+ nodes = nodes.slice(0, limit);
2699
+ state.bounded = true;
2700
+ }
2701
+ const sections = groupedNodeSections(nodes, state);
2702
+ const shown = nodes.length;
2703
+ return assemble(
2704
+ `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
2705
+ sections,
2706
+ state
2707
+ );
2708
+ }
2709
+ function renderTraversal(response) {
2710
+ const payload = obj(response);
2711
+ if (!payload) return void 0;
2712
+ const container = obj(payload.graph) ?? payload;
2713
+ const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
2714
+ const nodes = rawNodes.filter(isNodeShaped);
2715
+ const paths = Array.isArray(payload.paths) ? payload.paths : [];
2716
+ if (nodes.length === 0 && paths.length === 0) return void 0;
2717
+ const state = { bounded: false };
2718
+ const sections = [];
2719
+ const labelByUid = /* @__PURE__ */ new Map();
2720
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
2721
+ if (paths.length > 0) {
2722
+ const items = paths.map((raw) => obj(raw)).filter((path6) => Boolean(path6)).map((path6, index) => {
2723
+ const uids = Array.isArray(path6.node_uids) ? path6.node_uids.map(String) : [];
2724
+ const labels = Array.isArray(path6.labels) ? path6.labels.map(String) : Array.isArray(path6.nodes) ? path6.nodes.map((hop) => {
2725
+ const hopNode = obj(hop);
2726
+ return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
2727
+ }) : [];
2728
+ const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
2729
+ const cost = num(path6.cost) ?? num(path6.path_cost);
2730
+ const confidence = num(path6.path_confidence);
2731
+ const meta = [
2732
+ cost !== void 0 ? `cost ${cost}` : void 0,
2733
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
2734
+ ].filter(Boolean);
2735
+ const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
2736
+ return labels.length > 0 && uids.length > 0 ? `${head}
2737
+ uids: ${uids.join(" \u2192 ")}` : head;
2738
+ });
2739
+ sections.push({ heading: "## Paths", items });
2740
+ }
2741
+ if (nodes.length > 0) {
2742
+ sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
2743
+ const relationships = relationshipItems(
2744
+ Array.isArray(container.edges) ? container.edges : [],
2745
+ labelByUid,
2746
+ state
2747
+ );
2748
+ if (relationships.length > 0) {
2749
+ sections.push({ heading: "## Relationships", items: relationships });
2750
+ }
2751
+ }
2752
+ return assemble("# Graph traversal", sections, state);
2753
+ }
2754
+ var LABEL_MAX, SUMMARY_MAX, QUOTE_MAX, ARTICLE_MAX, CHUNK_MAX, RELATIONSHIP_MAX, RENDER_CHAR_BUDGET, CURATION_EDGE_TYPES, INFRA_NODE_TYPES;
2755
+ var init_render = __esm({
2756
+ "src/render.ts"() {
2757
+ "use strict";
2758
+ LABEL_MAX = 200;
2759
+ SUMMARY_MAX = 400;
2760
+ QUOTE_MAX = 180;
2761
+ ARTICLE_MAX = 6e3;
2762
+ CHUNK_MAX = 6e3;
2763
+ RELATIONSHIP_MAX = 40;
2764
+ RENDER_CHAR_BUDGET = 24e3;
2765
+ CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
2766
+ INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
2767
+ }
2768
+ });
2769
+
2415
2770
  // src/tools.ts
2416
2771
  function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
2417
2772
  if (profile === "coding") return TOOLS;
@@ -3005,6 +3360,7 @@ async function handlePlan(client2, args) {
3005
3360
  async function handleRetrieve(client2, args) {
3006
3361
  const {
3007
3362
  action,
3363
+ format,
3008
3364
  query,
3009
3365
  start_uid,
3010
3366
  end_uid,
@@ -3025,12 +3381,24 @@ async function handleRetrieve(client2, args) {
3025
3381
  graph_max_depth,
3026
3382
  confidence_min,
3027
3383
  salience_min,
3384
+ article_limit,
3385
+ valid_at,
3386
+ created_after,
3387
+ created_before,
3388
+ explain,
3389
+ k,
3390
+ max_cost,
3028
3391
  agent_id
3029
3392
  } = args;
3393
+ const rendered = (raw, render) => {
3394
+ if (format === "json") return ok3(raw);
3395
+ const text = render(raw);
3396
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3397
+ };
3030
3398
  switch (action) {
3031
3399
  case "context":
3032
3400
  if (!query) return err3("query is required for context retrieval");
3033
- return ok3(
3401
+ return rendered(
3034
3402
  await client2.retrieveContext({
3035
3403
  query,
3036
3404
  node_limit: limit,
@@ -3039,110 +3407,139 @@ async function handleRetrieve(client2, args) {
3039
3407
  chunk_limit: include_chunks ? limit ?? 5 : 0,
3040
3408
  // Scoped by default, never walled: the coding profile drops the
3041
3409
  // document/article leg (the "Bob Work" class of noise) unless the
3042
- // model opts in for a genuinely document-shaped question.
3043
- ...process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
3410
+ // model opts in for a genuinely document-shaped question. Any
3411
+ // profile can drop it with include_documents: false (previously a
3412
+ // silent no-op outside coding). An EXPLICIT article_limit is at
3413
+ // least as clear an opt-in as include_documents:true — it always
3414
+ // wins over profile defaults (a coding-profile model reaching for
3415
+ // the advertised knob must not get a silent no-op).
3416
+ ...article_limit !== void 0 ? { article_limit } : include_documents === false || process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
3417
+ valid_at,
3044
3418
  include_graph,
3045
3419
  graph_expansion_limit: graph_expansion_limit ?? 3,
3046
3420
  graph_max_depth: graph_max_depth ?? 2
3047
- })
3421
+ }),
3422
+ renderContext
3048
3423
  );
3049
3424
  case "text":
3050
3425
  if (!query) return err3("query is required for text search");
3051
- return ok3(
3426
+ return rendered(
3052
3427
  await client2.retrieve({
3053
3428
  action: "text",
3054
3429
  query,
3055
3430
  limit,
3056
3431
  node_types,
3057
3432
  layer,
3433
+ explain,
3058
3434
  agent_id
3059
- })
3435
+ }),
3436
+ (raw) => renderNodeList(raw, `Text search: ${query}`)
3060
3437
  );
3061
3438
  case "semantic":
3062
3439
  if (!query) return err3("query is required for semantic search");
3063
- return ok3(
3440
+ return rendered(
3064
3441
  await client2.retrieve({
3065
3442
  action: "semantic",
3066
3443
  query,
3067
3444
  k: limit,
3068
3445
  node_types,
3069
3446
  layer,
3447
+ explain,
3070
3448
  agent_id
3071
- })
3449
+ }),
3450
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`)
3072
3451
  );
3073
3452
  case "hybrid":
3074
3453
  if (!query) return err3("query is required for hybrid search");
3075
- return ok3(
3454
+ return rendered(
3076
3455
  await client2.retrieve({
3077
3456
  action: "hybrid",
3078
3457
  query,
3079
3458
  k: limit,
3080
3459
  node_types,
3081
3460
  layer,
3461
+ explain,
3082
3462
  agent_id
3083
- })
3463
+ }),
3464
+ // With explain, per-leg scoring must survive — raw JSON only.
3465
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
3084
3466
  );
3085
- // Structured queries
3467
+ // Structured queries. These go through the generic /retrieve action
3468
+ // dispatch rather than the SDK's no-argument convenience methods — the
3469
+ // conveniences silently dropped `limit`/`offset` (the tool advertised
3470
+ // them; five actions ignored them).
3086
3471
  case "active_goals":
3087
- return ok3(await client2.getGoals());
3088
3472
  case "open_questions":
3089
- return ok3(await client2.getOpenQuestions());
3090
3473
  case "weak_claims":
3091
- return ok3(await client2.getWeakClaims());
3092
3474
  case "pending_approvals":
3093
- return ok3(await client2.getPendingApprovals());
3094
3475
  case "unresolved_contradictions":
3095
- return ok3(await client2.getContradictions());
3476
+ return rendered(
3477
+ await client2.retrieve({
3478
+ action,
3479
+ limit,
3480
+ offset,
3481
+ agent_id
3482
+ }),
3483
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
3484
+ );
3096
3485
  case "stale_derivations":
3097
- return ok3(
3486
+ return rendered(
3098
3487
  await client2.retrieve({
3099
3488
  action: "stale_derivations",
3100
3489
  limit,
3101
3490
  offset
3102
- })
3491
+ }),
3492
+ (raw) => renderNodeList(raw, "stale derivations", limit)
3103
3493
  );
3104
3494
  case "preferences":
3105
- return ok3(
3495
+ return rendered(
3106
3496
  await client2.retrieve({
3107
3497
  action: "preferences",
3108
3498
  ...query ? { query, k: limit } : {},
3109
3499
  limit,
3110
3500
  agent_id
3111
- })
3501
+ }),
3502
+ (raw) => renderNodeList(raw, "Preferences", limit)
3112
3503
  );
3113
3504
  case "layer":
3114
3505
  if (!layer) return err3("layer is required for layer retrieval");
3115
- return ok3(
3506
+ return rendered(
3116
3507
  await client2.retrieve({
3117
3508
  action: "layer",
3118
3509
  layer,
3119
3510
  limit,
3511
+ node_types,
3120
3512
  agent_id
3121
- })
3513
+ }),
3514
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
3122
3515
  );
3123
3516
  case "recent":
3124
- return ok3(
3517
+ return rendered(
3125
3518
  await client2.retrieve({
3126
3519
  action: "recent",
3127
3520
  limit,
3128
3521
  node_types,
3129
3522
  confidence_min,
3130
3523
  salience_min,
3524
+ created_after,
3525
+ created_before,
3131
3526
  agent_id
3132
- })
3527
+ }),
3528
+ (raw) => renderNodeList(raw, "Recently ingested", limit)
3133
3529
  );
3134
3530
  // Document inventory
3135
3531
  case "document_index":
3136
- return ok3(
3532
+ return rendered(
3137
3533
  await client2.getNodes({
3138
3534
  node_type: "Document",
3139
3535
  limit: limit ?? 100
3140
- })
3536
+ }),
3537
+ (raw) => renderNodeList(raw, "Ingested documents")
3141
3538
  );
3142
3539
  // Graph traversal
3143
3540
  case "chain":
3144
3541
  if (!start_uid) return err3("start_uid is required for chain traversal");
3145
- return ok3(
3542
+ return rendered(
3146
3543
  await client2.traverse({
3147
3544
  action: "chain",
3148
3545
  start_uid,
@@ -3150,11 +3547,12 @@ async function handleRetrieve(client2, args) {
3150
3547
  exclude_edge_types,
3151
3548
  include_provenance,
3152
3549
  max_nodes
3153
- })
3550
+ }),
3551
+ renderTraversal
3154
3552
  );
3155
3553
  case "neighborhood":
3156
3554
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3157
- return ok3(
3555
+ return rendered(
3158
3556
  await client2.traverse({
3159
3557
  action: "neighborhood",
3160
3558
  start_uid,
@@ -3164,12 +3562,13 @@ async function handleRetrieve(client2, args) {
3164
3562
  exclude_edge_types,
3165
3563
  include_provenance,
3166
3564
  max_nodes
3167
- })
3565
+ }),
3566
+ renderTraversal
3168
3567
  );
3169
3568
  case "path":
3170
3569
  if (!start_uid) return err3("start_uid is required for path traversal");
3171
3570
  if (!end_uid) return err3("end_uid is required for path traversal");
3172
- return ok3(
3571
+ return rendered(
3173
3572
  await client2.traverse({
3174
3573
  action: "path",
3175
3574
  start_uid,
@@ -3180,11 +3579,12 @@ async function handleRetrieve(client2, args) {
3180
3579
  exclude_edge_types,
3181
3580
  include_provenance,
3182
3581
  max_nodes
3183
- })
3582
+ }),
3583
+ renderTraversal
3184
3584
  );
3185
3585
  case "subgraph":
3186
3586
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
3187
- return ok3(
3587
+ return rendered(
3188
3588
  await client2.traverse({
3189
3589
  action: "subgraph",
3190
3590
  start_uid,
@@ -3194,7 +3594,24 @@ async function handleRetrieve(client2, args) {
3194
3594
  exclude_edge_types,
3195
3595
  include_provenance,
3196
3596
  max_nodes
3197
- })
3597
+ }),
3598
+ renderTraversal
3599
+ );
3600
+ case "top_k_paths":
3601
+ if (!start_uid) return err3("start_uid is required for top_k_paths");
3602
+ if (!end_uid) return err3("end_uid is required for top_k_paths");
3603
+ return rendered(
3604
+ await client2.traverse({
3605
+ action: "top_k_paths",
3606
+ start_uid,
3607
+ end_uid,
3608
+ k,
3609
+ max_hops: max_depth,
3610
+ max_cost,
3611
+ edge_types,
3612
+ exclude_edge_types
3613
+ }),
3614
+ renderTraversal
3198
3615
  );
3199
3616
  default:
3200
3617
  return err3(`Unknown retrieve action: ${action}`);
@@ -3440,6 +3857,7 @@ var init_tools = __esm({
3440
3857
  init_code_tool();
3441
3858
  init_codegraph();
3442
3859
  init_sync_tool();
3860
+ init_render();
3443
3861
  TOOLS = [
3444
3862
  {
3445
3863
  name: "mindgraph_capture",
@@ -4013,10 +4431,16 @@ var init_tools = __esm({
4013
4431
  "neighborhood",
4014
4432
  "path",
4015
4433
  "subgraph",
4434
+ "top_k_paths",
4016
4435
  "document_index"
4017
4436
  ],
4018
4437
  description: "Retrieval or traversal strategy"
4019
4438
  },
4439
+ format: {
4440
+ type: "string",
4441
+ enum: ["text", "json"],
4442
+ 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)."
4443
+ },
4020
4444
  query: {
4021
4445
  type: "string",
4022
4446
  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."
@@ -4076,7 +4500,35 @@ var init_tools = __esm({
4076
4500
  },
4077
4501
  include_documents: {
4078
4502
  type: "boolean",
4079
- 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."
4503
+ 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."
4504
+ },
4505
+ article_limit: {
4506
+ type: "number",
4507
+ 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."
4508
+ },
4509
+ valid_at: {
4510
+ type: "string",
4511
+ description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
4512
+ },
4513
+ created_after: {
4514
+ type: "number",
4515
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
4516
+ },
4517
+ created_before: {
4518
+ type: "number",
4519
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested before this time"
4520
+ },
4521
+ explain: {
4522
+ type: "boolean",
4523
+ description: "hybrid action: include per-leg retrieval scoring (legs) in the response"
4524
+ },
4525
+ k: {
4526
+ type: "number",
4527
+ description: "top_k_paths action: number of cheapest paths (default 3, cap 25)"
4528
+ },
4529
+ max_cost: {
4530
+ type: "number",
4531
+ description: "top_k_paths action: optional cost ceiling for returned paths"
4080
4532
  },
4081
4533
  include_chunks: {
4082
4534
  type: "boolean",
@@ -4907,7 +5359,7 @@ async function main() {
4907
5359
  const transport = new import_stdio.StdioServerTransport();
4908
5360
  await server.connect(transport);
4909
5361
  console.error(
4910
- `MindGraph MCP server v${"0.15.0"} running on stdio (${BASE_URL})`
5362
+ `MindGraph MCP server v${"0.16.0"} running on stdio (${BASE_URL})`
4911
5363
  );
4912
5364
  }
4913
5365
  var import_server, import_stdio, import_types, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, server, GOVERNANCE_CONFIG, PROMPTS;
@@ -5047,7 +5499,7 @@ Capture knowledge when the user shares something worth remembering:
5047
5499
  server = new import_server.Server(
5048
5500
  {
5049
5501
  name: "mindgraph",
5050
- version: "0.15.0"
5502
+ version: "0.16.0"
5051
5503
  },
5052
5504
  {
5053
5505
  capabilities: {