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/index.js CHANGED
@@ -1474,6 +1474,355 @@ async function scanRepository(remote, repository, requested, provider, agentId)
1474
1474
  };
1475
1475
  }
1476
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
+
1477
1826
  // src/tools.ts
1478
1827
  var TOOLS = [
1479
1828
  {
@@ -2048,10 +2397,16 @@ var TOOLS = [
2048
2397
  "neighborhood",
2049
2398
  "path",
2050
2399
  "subgraph",
2400
+ "top_k_paths",
2051
2401
  "document_index"
2052
2402
  ],
2053
2403
  description: "Retrieval or traversal strategy"
2054
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
+ },
2055
2410
  query: {
2056
2411
  type: "string",
2057
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."
@@ -2111,7 +2466,35 @@ var TOOLS = [
2111
2466
  },
2112
2467
  include_documents: {
2113
2468
  type: "boolean",
2114
- 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"
2115
2498
  },
2116
2499
  include_chunks: {
2117
2500
  type: "boolean",
@@ -3017,6 +3400,7 @@ async function handlePlan(client2, args) {
3017
3400
  async function handleRetrieve(client2, args) {
3018
3401
  const {
3019
3402
  action,
3403
+ format,
3020
3404
  query,
3021
3405
  start_uid,
3022
3406
  end_uid,
@@ -3037,12 +3421,24 @@ async function handleRetrieve(client2, args) {
3037
3421
  graph_max_depth,
3038
3422
  confidence_min,
3039
3423
  salience_min,
3424
+ article_limit,
3425
+ valid_at,
3426
+ created_after,
3427
+ created_before,
3428
+ explain,
3429
+ k,
3430
+ max_cost,
3040
3431
  agent_id
3041
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
+ };
3042
3438
  switch (action) {
3043
3439
  case "context":
3044
3440
  if (!query) return err3("query is required for context retrieval");
3045
- return ok3(
3441
+ return rendered(
3046
3442
  await client2.retrieveContext({
3047
3443
  query,
3048
3444
  node_limit: limit,
@@ -3051,110 +3447,139 @@ async function handleRetrieve(client2, args) {
3051
3447
  chunk_limit: include_chunks ? limit ?? 5 : 0,
3052
3448
  // Scoped by default, never walled: the coding profile drops the
3053
3449
  // document/article leg (the "Bob Work" class of noise) unless the
3054
- // model opts in for a genuinely document-shaped question.
3055
- ...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,
3056
3458
  include_graph,
3057
3459
  graph_expansion_limit: graph_expansion_limit ?? 3,
3058
3460
  graph_max_depth: graph_max_depth ?? 2
3059
- })
3461
+ }),
3462
+ renderContext
3060
3463
  );
3061
3464
  case "text":
3062
3465
  if (!query) return err3("query is required for text search");
3063
- return ok3(
3466
+ return rendered(
3064
3467
  await client2.retrieve({
3065
3468
  action: "text",
3066
3469
  query,
3067
3470
  limit,
3068
3471
  node_types,
3069
3472
  layer,
3473
+ explain,
3070
3474
  agent_id
3071
- })
3475
+ }),
3476
+ (raw) => renderNodeList(raw, `Text search: ${query}`)
3072
3477
  );
3073
3478
  case "semantic":
3074
3479
  if (!query) return err3("query is required for semantic search");
3075
- return ok3(
3480
+ return rendered(
3076
3481
  await client2.retrieve({
3077
3482
  action: "semantic",
3078
3483
  query,
3079
3484
  k: limit,
3080
3485
  node_types,
3081
3486
  layer,
3487
+ explain,
3082
3488
  agent_id
3083
- })
3489
+ }),
3490
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`)
3084
3491
  );
3085
3492
  case "hybrid":
3086
3493
  if (!query) return err3("query is required for hybrid search");
3087
- return ok3(
3494
+ return rendered(
3088
3495
  await client2.retrieve({
3089
3496
  action: "hybrid",
3090
3497
  query,
3091
3498
  k: limit,
3092
3499
  node_types,
3093
3500
  layer,
3501
+ explain,
3094
3502
  agent_id
3095
- })
3503
+ }),
3504
+ // With explain, per-leg scoring must survive — raw JSON only.
3505
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
3096
3506
  );
3097
- // 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).
3098
3511
  case "active_goals":
3099
- return ok3(await client2.getGoals());
3100
3512
  case "open_questions":
3101
- return ok3(await client2.getOpenQuestions());
3102
3513
  case "weak_claims":
3103
- return ok3(await client2.getWeakClaims());
3104
3514
  case "pending_approvals":
3105
- return ok3(await client2.getPendingApprovals());
3106
3515
  case "unresolved_contradictions":
3107
- 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
+ );
3108
3525
  case "stale_derivations":
3109
- return ok3(
3526
+ return rendered(
3110
3527
  await client2.retrieve({
3111
3528
  action: "stale_derivations",
3112
3529
  limit,
3113
3530
  offset
3114
- })
3531
+ }),
3532
+ (raw) => renderNodeList(raw, "stale derivations", limit)
3115
3533
  );
3116
3534
  case "preferences":
3117
- return ok3(
3535
+ return rendered(
3118
3536
  await client2.retrieve({
3119
3537
  action: "preferences",
3120
3538
  ...query ? { query, k: limit } : {},
3121
3539
  limit,
3122
3540
  agent_id
3123
- })
3541
+ }),
3542
+ (raw) => renderNodeList(raw, "Preferences", limit)
3124
3543
  );
3125
3544
  case "layer":
3126
3545
  if (!layer) return err3("layer is required for layer retrieval");
3127
- return ok3(
3546
+ return rendered(
3128
3547
  await client2.retrieve({
3129
3548
  action: "layer",
3130
3549
  layer,
3131
3550
  limit,
3551
+ node_types,
3132
3552
  agent_id
3133
- })
3553
+ }),
3554
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
3134
3555
  );
3135
3556
  case "recent":
3136
- return ok3(
3557
+ return rendered(
3137
3558
  await client2.retrieve({
3138
3559
  action: "recent",
3139
3560
  limit,
3140
3561
  node_types,
3141
3562
  confidence_min,
3142
3563
  salience_min,
3564
+ created_after,
3565
+ created_before,
3143
3566
  agent_id
3144
- })
3567
+ }),
3568
+ (raw) => renderNodeList(raw, "Recently ingested", limit)
3145
3569
  );
3146
3570
  // Document inventory
3147
3571
  case "document_index":
3148
- return ok3(
3572
+ return rendered(
3149
3573
  await client2.getNodes({
3150
3574
  node_type: "Document",
3151
3575
  limit: limit ?? 100
3152
- })
3576
+ }),
3577
+ (raw) => renderNodeList(raw, "Ingested documents")
3153
3578
  );
3154
3579
  // Graph traversal
3155
3580
  case "chain":
3156
3581
  if (!start_uid) return err3("start_uid is required for chain traversal");
3157
- return ok3(
3582
+ return rendered(
3158
3583
  await client2.traverse({
3159
3584
  action: "chain",
3160
3585
  start_uid,
@@ -3162,11 +3587,12 @@ async function handleRetrieve(client2, args) {
3162
3587
  exclude_edge_types,
3163
3588
  include_provenance,
3164
3589
  max_nodes
3165
- })
3590
+ }),
3591
+ renderTraversal
3166
3592
  );
3167
3593
  case "neighborhood":
3168
3594
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3169
- return ok3(
3595
+ return rendered(
3170
3596
  await client2.traverse({
3171
3597
  action: "neighborhood",
3172
3598
  start_uid,
@@ -3176,12 +3602,13 @@ async function handleRetrieve(client2, args) {
3176
3602
  exclude_edge_types,
3177
3603
  include_provenance,
3178
3604
  max_nodes
3179
- })
3605
+ }),
3606
+ renderTraversal
3180
3607
  );
3181
3608
  case "path":
3182
3609
  if (!start_uid) return err3("start_uid is required for path traversal");
3183
3610
  if (!end_uid) return err3("end_uid is required for path traversal");
3184
- return ok3(
3611
+ return rendered(
3185
3612
  await client2.traverse({
3186
3613
  action: "path",
3187
3614
  start_uid,
@@ -3192,11 +3619,12 @@ async function handleRetrieve(client2, args) {
3192
3619
  exclude_edge_types,
3193
3620
  include_provenance,
3194
3621
  max_nodes
3195
- })
3622
+ }),
3623
+ renderTraversal
3196
3624
  );
3197
3625
  case "subgraph":
3198
3626
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
3199
- return ok3(
3627
+ return rendered(
3200
3628
  await client2.traverse({
3201
3629
  action: "subgraph",
3202
3630
  start_uid,
@@ -3206,7 +3634,24 @@ async function handleRetrieve(client2, args) {
3206
3634
  exclude_edge_types,
3207
3635
  include_provenance,
3208
3636
  max_nodes
3209
- })
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
3210
3655
  );
3211
3656
  default:
3212
3657
  return err3(`Unknown retrieve action: ${action}`);
@@ -4059,7 +4504,7 @@ if (PROFILE === "coding") {
4059
4504
  var server = new import_server.Server(
4060
4505
  {
4061
4506
  name: "mindgraph",
4062
- version: "0.15.0"
4507
+ version: "0.16.0"
4063
4508
  },
4064
4509
  {
4065
4510
  capabilities: {
@@ -4273,7 +4718,7 @@ async function main() {
4273
4718
  const transport = new import_stdio.StdioServerTransport();
4274
4719
  await server.connect(transport);
4275
4720
  console.error(
4276
- `MindGraph MCP server v${"0.15.0"} running on stdio (${BASE_URL})`
4721
+ `MindGraph MCP server v${"0.16.0"} running on stdio (${BASE_URL})`
4277
4722
  );
4278
4723
  }
4279
4724
  main().catch((error) => {