mindgraph-mcp 0.15.0 → 0.17.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
@@ -777,13 +777,13 @@ function codeRefIdentityKey(ref) {
777
777
 
778
778
  // src/code-tool.ts
779
779
  var ok = (data) => ({
780
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
780
+ content: [{ type: "text", text: JSON.stringify(data) }]
781
781
  });
782
782
  var err = (code, message, details) => ({
783
783
  content: [
784
784
  {
785
785
  type: "text",
786
- text: JSON.stringify({ error: message, code, details }, null, 2)
786
+ text: JSON.stringify({ error: message, code, details })
787
787
  }
788
788
  ],
789
789
  isError: true
@@ -1272,7 +1272,7 @@ var import_node_crypto3 = __toESM(require("crypto"));
1272
1272
  var import_node_fs3 = __toESM(require("fs"));
1273
1273
  var import_node_path3 = __toESM(require("path"));
1274
1274
  var ok2 = (data) => ({
1275
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1275
+ content: [{ type: "text", text: JSON.stringify(data) }]
1276
1276
  });
1277
1277
  var err2 = (code, message) => ({
1278
1278
  content: [{ type: "text", text: JSON.stringify({ code, error: message }) }],
@@ -1474,6 +1474,517 @@ 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(
1506
+ node && str(node.uid) && (str(node.label) || str(node.node_type) || obj(node.node_type))
1507
+ );
1508
+ }
1509
+ function nodeDisplayType(node) {
1510
+ const props = obj(node.props);
1511
+ const plain = str(node.node_type);
1512
+ return (plain && plain !== "Custom" ? plain : void 0) ?? str(obj(node.node_type)?.Custom) ?? str(props?.object_type) ?? str(props?.type_name) ?? str(obj(props?.data)?.domain_type) ?? plain ?? "Node";
1513
+ }
1514
+ function nodeFields(node) {
1515
+ const props = obj(node.props);
1516
+ return obj(props?.fields) ?? obj(obj(props?.data)?.fields);
1517
+ }
1518
+ function unwrapSearchResult(value) {
1519
+ if (isNodeShaped(value)) return value;
1520
+ const wrapper = obj(value);
1521
+ const inner = obj(wrapper?.node) ?? obj(wrapper?.object);
1522
+ if (inner && isNodeShaped(inner)) {
1523
+ const score = num(wrapper.score);
1524
+ return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
1525
+ }
1526
+ return void 0;
1527
+ }
1528
+ function statusTag(node) {
1529
+ const tags = [];
1530
+ if (node.superseded === true) {
1531
+ tags.push(
1532
+ node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
1533
+ );
1534
+ }
1535
+ const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
1536
+ if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
1537
+ if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
1538
+ if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
1539
+ return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
1540
+ }
1541
+ function firstQuote(node, state) {
1542
+ const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
1543
+ for (const raw of chunks) {
1544
+ const chunk = obj(raw);
1545
+ const quote = str(chunk?.quote) ?? str(obj(chunk?.anchor)?.exact);
1546
+ if (quote) return clip(quote, QUOTE_MAX, state);
1547
+ }
1548
+ return void 0;
1549
+ }
1550
+ function sourceTag(node) {
1551
+ const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
1552
+ const first = obj(docs[0]);
1553
+ const title = str(first?.title);
1554
+ if (!title) return void 0;
1555
+ const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
1556
+ return `from: ${title}${extra}`;
1557
+ }
1558
+ function layer7Fields(node, state) {
1559
+ const fields = nodeFields(node);
1560
+ if (!fields) return void 0;
1561
+ const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
1562
+ if (pairs.length === 0) return void 0;
1563
+ return clip(pairs.join("; "), SUMMARY_MAX, state);
1564
+ }
1565
+ function nodeSummaryText(node, state) {
1566
+ const props = obj(node.props);
1567
+ 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);
1568
+ }
1569
+ function nodeLines(node, state) {
1570
+ const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
1571
+ const type = nodeDisplayType(node);
1572
+ const confidence = num(node.confidence);
1573
+ const score = num(node.retrieval_score);
1574
+ const parenthetical = [
1575
+ type,
1576
+ confidence !== void 0 ? `confidence ${confidence}` : void 0,
1577
+ score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
1578
+ ].filter(Boolean).join(", ");
1579
+ const head = `- **${label}** [${str(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
1580
+ const summary = nodeSummaryText(node, state);
1581
+ const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
1582
+ const fields = layer7Fields(node, state);
1583
+ if (fields) lines.push(` - fields: ${fields}`);
1584
+ const quote = firstQuote(node, state);
1585
+ if (quote) lines.push(` - source quote: "${quote}"`);
1586
+ const source = sourceTag(node);
1587
+ if (source) lines.push(` - ${source}`);
1588
+ const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
1589
+ if (believers.length > 0) {
1590
+ const rendered2 = believers.slice(0, 3).map((raw) => {
1591
+ const believer = obj(raw);
1592
+ const conf = num(believer?.confidence);
1593
+ return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
1594
+ }).join(", ");
1595
+ lines.push(` - believed by: ${rendered2}`);
1596
+ }
1597
+ return lines.join("\n");
1598
+ }
1599
+ function groupedNodeSections(nodes, state, headingPrefix = "") {
1600
+ const groups = /* @__PURE__ */ new Map();
1601
+ for (const node of nodes) {
1602
+ const type = nodeDisplayType(node);
1603
+ const group = groups.get(type) ?? [];
1604
+ group.push(node);
1605
+ groups.set(type, group);
1606
+ }
1607
+ const sections = [];
1608
+ let first = true;
1609
+ for (const [type, group] of groups) {
1610
+ sections.push({
1611
+ heading: `${first ? headingPrefix : ""}### ${type}`,
1612
+ items: group.map((node) => nodeLines(node, state))
1613
+ });
1614
+ first = false;
1615
+ }
1616
+ return sections;
1617
+ }
1618
+ function relationshipItems(edges, labelByUid, state, options) {
1619
+ const lines = [];
1620
+ for (const raw of edges) {
1621
+ const edge = obj(raw);
1622
+ if (!edge) continue;
1623
+ const type = str(edge.edge_type) || "?";
1624
+ const from = str(edge.from_uid);
1625
+ const to = str(edge.to_uid);
1626
+ if (CURATION_EDGE_TYPES.has(type)) continue;
1627
+ if (!from || !to || from === to) continue;
1628
+ const fromLabel = labelByUid.get(from) ?? (options?.fallbackToUid ? `[${from}]` : void 0);
1629
+ const toLabel = labelByUid.get(to) ?? (options?.fallbackToUid ? `[${to}]` : void 0);
1630
+ if (!fromLabel || !toLabel) continue;
1631
+ lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
1632
+ }
1633
+ if (lines.length > RELATIONSHIP_MAX) {
1634
+ state.bounded = true;
1635
+ const extra = lines.length - RELATIONSHIP_MAX;
1636
+ return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
1637
+ }
1638
+ return lines;
1639
+ }
1640
+ function assemble(header, sections, state) {
1641
+ const lines = [header];
1642
+ let used = header.length;
1643
+ for (const section of sections) {
1644
+ if (section.items.length === 0 && !section.heading) continue;
1645
+ const headingCost = section.heading.length + 2;
1646
+ if (used + headingCost > RENDER_CHAR_BUDGET) {
1647
+ state.bounded = true;
1648
+ continue;
1649
+ }
1650
+ let sectionUsed = 0;
1651
+ const kept = [];
1652
+ let dropped = 0;
1653
+ for (const item of section.items) {
1654
+ if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
1655
+ dropped += 1;
1656
+ continue;
1657
+ }
1658
+ kept.push(item);
1659
+ sectionUsed += item.length + 1;
1660
+ }
1661
+ if (kept.length === 0 && section.items.length > 0) {
1662
+ state.bounded = true;
1663
+ continue;
1664
+ }
1665
+ if (dropped > 0) {
1666
+ state.bounded = true;
1667
+ const marker = `(+${dropped} more not shown)`;
1668
+ kept.push(marker);
1669
+ sectionUsed += marker.length + 1;
1670
+ }
1671
+ lines.push(
1672
+ section.items.length === 0 ? section.heading : section.heading ? `${section.heading}
1673
+ ${kept.join("\n")}` : kept.join("\n")
1674
+ );
1675
+ used += headingCost + sectionUsed;
1676
+ }
1677
+ if (state.bounded) {
1678
+ lines.push(
1679
+ '\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
1680
+ );
1681
+ }
1682
+ return lines.join("\n\n");
1683
+ }
1684
+ function policySections(payload, state) {
1685
+ const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
1686
+ if (policies.length === 0) return [];
1687
+ const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
1688
+ const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
1689
+ return `- ${str(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
1690
+ });
1691
+ return [{ heading: "## Applicable policies", items }];
1692
+ }
1693
+ function hiddenSections(payload) {
1694
+ const hidden = payload.hidden_item_count;
1695
+ if (hidden === void 0 || hidden === null) return [];
1696
+ return [
1697
+ {
1698
+ heading: `Note: ${hidden} item(s) were hidden by access scoping.`,
1699
+ items: []
1700
+ }
1701
+ ];
1702
+ }
1703
+ function renderContext(response) {
1704
+ const payload = obj(response);
1705
+ if (!payload) return void 0;
1706
+ const graph = obj(payload.graph);
1707
+ const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
1708
+ const shaped = rawNodes.filter(isNodeShaped);
1709
+ const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str(node.node_type) || ""));
1710
+ const infraOnly = shaped.length - nodes.length;
1711
+ const articles = Array.isArray(payload.articles) ? payload.articles : [];
1712
+ const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
1713
+ const state = { bounded: false };
1714
+ const sections = [];
1715
+ if (articles.length > 0) {
1716
+ sections.push({
1717
+ heading: "## Knowledge articles",
1718
+ items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
1719
+ (article) => `### ${str(article.label) || "(untitled)"} [${str(article.uid) || "?"}]
1720
+ ${clip(article.content, ARTICLE_MAX, state) || ""}`
1721
+ )
1722
+ });
1723
+ }
1724
+ if (nodes.length > 0) {
1725
+ sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
1726
+ const labelByUid = /* @__PURE__ */ new Map();
1727
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
1728
+ const relationships = relationshipItems(
1729
+ Array.isArray(graph?.edges) ? graph.edges : [],
1730
+ labelByUid,
1731
+ state
1732
+ );
1733
+ if (relationships.length > 0) {
1734
+ sections.push({ heading: "## Relationships", items: relationships });
1735
+ }
1736
+ } else if (infraOnly > 0) {
1737
+ sections.push({
1738
+ heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
1739
+ items: []
1740
+ });
1741
+ }
1742
+ if (chunks.length > 0) {
1743
+ sections.push({
1744
+ heading: "## Source excerpts",
1745
+ items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
1746
+ (chunk) => `- [${str(chunk.chunk_uid) || "?"}] from "${str(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
1747
+ )
1748
+ });
1749
+ }
1750
+ sections.push(...policySections(payload, state));
1751
+ sections.push(...hiddenSections(payload));
1752
+ if (sections.length === 0) {
1753
+ return "No results found for this search.";
1754
+ }
1755
+ if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
1756
+ return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
1757
+ }
1758
+ return assemble("# Retrieved context", sections, state);
1759
+ }
1760
+ function renderNodeList(response, title, limit) {
1761
+ let candidates = [];
1762
+ if (Array.isArray(response)) {
1763
+ candidates = response;
1764
+ } else {
1765
+ const payload = obj(response);
1766
+ if (!payload) return void 0;
1767
+ for (const key of ["nodes", "results", "items", "goals", "questions", "claims", "preferences", "documents"]) {
1768
+ const candidate = payload[key];
1769
+ if (Array.isArray(candidate) && candidate.some((entry) => unwrapSearchResult(entry))) {
1770
+ candidates = candidate;
1771
+ break;
1772
+ }
1773
+ }
1774
+ }
1775
+ let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
1776
+ if (nodes.length === 0) return void 0;
1777
+ const total = nodes.length;
1778
+ const state = { bounded: false };
1779
+ if (limit !== void 0 && limit > 0 && nodes.length > limit) {
1780
+ nodes = nodes.slice(0, limit);
1781
+ state.bounded = true;
1782
+ }
1783
+ const sections = groupedNodeSections(nodes, state);
1784
+ const shown = nodes.length;
1785
+ return assemble(
1786
+ `# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
1787
+ sections,
1788
+ state
1789
+ );
1790
+ }
1791
+ function renderTraversal(response) {
1792
+ const payload = obj(response);
1793
+ if (!payload) return void 0;
1794
+ const container = obj(payload.graph) ?? payload;
1795
+ const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
1796
+ const nodes = rawNodes.filter(isNodeShaped);
1797
+ const paths = Array.isArray(payload.paths) ? payload.paths : [];
1798
+ if (nodes.length === 0 && paths.length === 0) return void 0;
1799
+ const state = { bounded: false };
1800
+ const sections = [];
1801
+ const labelByUid = /* @__PURE__ */ new Map();
1802
+ for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
1803
+ if (paths.length > 0) {
1804
+ const items = paths.map((raw) => obj(raw)).filter((path3) => Boolean(path3)).map((path3, index) => {
1805
+ const uids = Array.isArray(path3.node_uids) ? path3.node_uids.map(String) : [];
1806
+ const labels = Array.isArray(path3.labels) ? path3.labels.map(String) : Array.isArray(path3.nodes) ? path3.nodes.map((hop) => {
1807
+ const hopNode = obj(hop);
1808
+ return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
1809
+ }) : [];
1810
+ const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
1811
+ const cost = num(path3.cost) ?? num(path3.path_cost);
1812
+ const confidence = num(path3.path_confidence);
1813
+ const meta = [
1814
+ cost !== void 0 ? `cost ${cost}` : void 0,
1815
+ confidence !== void 0 ? `confidence ${confidence}` : void 0
1816
+ ].filter(Boolean);
1817
+ const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
1818
+ return labels.length > 0 && uids.length > 0 ? `${head}
1819
+ uids: ${uids.join(" \u2192 ")}` : head;
1820
+ });
1821
+ sections.push({ heading: "## Paths", items });
1822
+ }
1823
+ if (nodes.length > 0) {
1824
+ sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
1825
+ const relationships = relationshipItems(
1826
+ Array.isArray(container.edges) ? container.edges : [],
1827
+ labelByUid,
1828
+ state
1829
+ );
1830
+ if (relationships.length > 0) {
1831
+ sections.push({ heading: "## Relationships", items: relationships });
1832
+ }
1833
+ }
1834
+ return assemble("# Graph traversal", sections, state);
1835
+ }
1836
+ function neighborLine(value) {
1837
+ const node = obj(value);
1838
+ if (!node) return void 0;
1839
+ const label = str(node.label) || "(unlabeled)";
1840
+ const uid = str(node.uid);
1841
+ const type = str(node.node_type);
1842
+ return `- ${label}${uid ? ` [${uid}]` : ""}${type ? ` (${type})` : ""}`;
1843
+ }
1844
+ function renderOntologyAnswer(response) {
1845
+ const payload = obj(response);
1846
+ if (!payload) return void 0;
1847
+ const objects = Array.isArray(payload.objects) ? payload.objects.filter(isNodeShaped) : isNodeShaped(payload.object) ? [payload.object] : [];
1848
+ const cognitive = obj(payload.cognitive_context);
1849
+ const provenance = Array.isArray(payload.provenance) ? payload.provenance : [];
1850
+ const hasQuerySignature = Array.isArray(payload.objects) || obj(payload.confidence) !== void 0;
1851
+ const cognitiveEmpty = !cognitive || Object.values(cognitive).every((v) => !Array.isArray(v) || v.length === 0);
1852
+ if (objects.length === 0 && cognitiveEmpty && provenance.length === 0) {
1853
+ return hasQuerySignature ? "No matching domain objects." : void 0;
1854
+ }
1855
+ const state = { bounded: false };
1856
+ const sections = [];
1857
+ if (objects.length > 0) {
1858
+ sections.push(...groupedNodeSections(objects, state));
1859
+ }
1860
+ const labelByUid = /* @__PURE__ */ new Map();
1861
+ for (const node of objects) labelByUid.set(str(node.uid), str(node.label) || "?");
1862
+ const graph = obj(payload.graph);
1863
+ for (const raw of Array.isArray(graph?.nodes) ? graph.nodes : []) {
1864
+ const node = obj(raw);
1865
+ const uid = str(node?.uid);
1866
+ if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
1867
+ }
1868
+ if (cognitive) {
1869
+ for (const rawNodes of Object.values(cognitive)) {
1870
+ if (!Array.isArray(rawNodes)) continue;
1871
+ for (const raw of rawNodes) {
1872
+ const node = obj(raw);
1873
+ const uid = str(node?.uid);
1874
+ if (uid && !labelByUid.has(uid)) labelByUid.set(uid, str(node?.label) || "?");
1875
+ }
1876
+ }
1877
+ }
1878
+ const relations = Array.isArray(payload.relations) ? payload.relations : [];
1879
+ const relationLines = relationshipItems(relations, labelByUid, state, { fallbackToUid: true });
1880
+ if (relationLines.length > 0) {
1881
+ sections.push({ heading: "## Relations", items: relationLines });
1882
+ }
1883
+ if (cognitive) {
1884
+ for (const [category, rawNodes] of Object.entries(cognitive)) {
1885
+ if (!Array.isArray(rawNodes) || rawNodes.length === 0) continue;
1886
+ const items = rawNodes.map(neighborLine).filter((line) => Boolean(line));
1887
+ if (items.length > 0) {
1888
+ sections.push({ heading: `### Cognitive context \u2014 ${category}`, items });
1889
+ }
1890
+ }
1891
+ }
1892
+ if (provenance.length > 0) {
1893
+ const items = provenance.map((raw) => obj(raw)).filter((entry) => Boolean(entry)).map((entry) => {
1894
+ const span = clip(entry.text_span, QUOTE_MAX, state) || "";
1895
+ const by = str(entry.ingested_by_name);
1896
+ const suffix = `${by ? ` (ingested by ${by})` : ""} [source ${str(entry.source_uid) || "?"}]`;
1897
+ return str(entry.span_kind) === "anchor" ? `- "${span}"${suffix}` : `- context: ${span}${suffix}`;
1898
+ });
1899
+ sections.push({ heading: "## Sources", items });
1900
+ }
1901
+ const confidence = num(obj(payload.confidence)?.overall);
1902
+ const notes = [];
1903
+ if (confidence !== void 0) notes.push(`overall confidence ${confidence}`);
1904
+ if (payload.has_more === true) notes.push("more results exist (has_more)");
1905
+ if (payload.seed_cap_hit === true) notes.push("seed cap hit \u2014 narrow the query for a complete set");
1906
+ if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
1907
+ return assemble(
1908
+ `# Ontology results (${objects.length} object${objects.length === 1 ? "" : "s"})`,
1909
+ sections,
1910
+ state
1911
+ );
1912
+ }
1913
+ function renderObjectList(response, title) {
1914
+ const payload = obj(response);
1915
+ if (!payload) return void 0;
1916
+ const rawItems = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.rows) && payload.rows.some((row) => unwrapSearchResult(row)) ? payload.rows : void 0;
1917
+ const items = (rawItems ?? []).map(unwrapSearchResult).filter((node) => Boolean(node));
1918
+ if (items.length === 0) {
1919
+ return rawItems ? `# ${title} (0 results)` : void 0;
1920
+ }
1921
+ const state = { bounded: false };
1922
+ const sections = groupedNodeSections(items, state);
1923
+ const total = num(payload.total_count);
1924
+ const truncations = Array.isArray(payload.truncation_reasons) ? payload.truncation_reasons.filter((reason) => typeof reason === "string") : [];
1925
+ const notes = [];
1926
+ if (payload.has_more === true) notes.push("more results exist");
1927
+ if (truncations.length > 0) notes.push(`truncated: ${truncations.join(", ")}`);
1928
+ if (notes.length > 0) sections.push({ heading: `Note: ${notes.join("; ")}.`, items: [] });
1929
+ const count = total !== void 0 && total !== items.length ? `${items.length} of ${total}` : `${items.length}`;
1930
+ return assemble(`# ${title} (${count})`, sections, state);
1931
+ }
1932
+ var JOBS_RENDER_MAX = 20;
1933
+ function renderJobs(response) {
1934
+ const jobs = Array.isArray(response) ? response : obj(response)?.jobs;
1935
+ if (!Array.isArray(jobs)) return void 0;
1936
+ if (jobs.length === 0) return "No ingestion jobs.";
1937
+ const state = { bounded: false };
1938
+ const sorted = jobs.map((raw) => obj(raw)).filter((job) => Boolean(job)).sort((a, b) => (num(b.created_at) ?? 0) - (num(a.created_at) ?? 0));
1939
+ const shown = sorted.slice(0, JOBS_RENDER_MAX);
1940
+ if (shown.length < sorted.length) state.bounded = true;
1941
+ const items = shown.map((job) => {
1942
+ const progress = obj(job.progress);
1943
+ const done = num(progress?.processed_chunks);
1944
+ const totalChunks = num(progress?.total_chunks);
1945
+ const bits = [
1946
+ str(job.status) || "?",
1947
+ done !== void 0 && totalChunks !== void 0 ? `${done}/${totalChunks} chunks` : void 0,
1948
+ num(progress?.nodes_created) !== void 0 ? `${num(progress?.nodes_created)} nodes` : void 0,
1949
+ num(job.queue_position) !== void 0 ? `queue #${num(job.queue_position)}` : void 0,
1950
+ str(job.error) ? `error: ${clip(job.error, 160, state)}` : void 0
1951
+ ].filter(Boolean);
1952
+ return `- [${str(job.id) || "?"}] ${clip(job.title, 120, state) || "(untitled)"} \u2014 ${bits.join(", ")}`;
1953
+ });
1954
+ return assemble(
1955
+ `# Ingestion jobs (${shown.length}${shown.length < sorted.length ? ` of ${sorted.length}` : ""})`,
1956
+ [{ heading: "", items }],
1957
+ state
1958
+ );
1959
+ }
1960
+ function renderSignals(response) {
1961
+ const payload = obj(response);
1962
+ if (!payload) return void 0;
1963
+ const state = { bounded: false };
1964
+ const sections = [];
1965
+ for (const [key, value] of Object.entries(payload)) {
1966
+ if (!Array.isArray(value) || value.length === 0) continue;
1967
+ const items = value.map((raw) => obj(raw)).filter((item) => Boolean(item)).map((item) => {
1968
+ const fromLabel = str(item.from_label);
1969
+ const toLabel = str(item.to_label);
1970
+ const anyLabelKey = Object.keys(item).find(
1971
+ (k) => k.endsWith("_label") && str(item[k])
1972
+ );
1973
+ const label = str(item.label) ?? str(item.title) ?? str(item.name) ?? (fromLabel && toLabel ? `${fromLabel} \u2194 ${toLabel}` : void 0) ?? (anyLabelKey ? str(item[anyLabelKey]) : void 0) ?? str(item.uid) ?? str(item.target_uid) ?? "(item)";
1974
+ const uid = str(item.uid) ?? str(item.target_uid);
1975
+ const numbers = Object.entries(item).filter(([, v]) => typeof v === "number").slice(0, 4).map(([k, v]) => `${k} ${Math.round(v * 100) / 100}`);
1976
+ const lists = Object.entries(item).filter(([, v]) => Array.isArray(v) && v.length > 0).slice(0, 2).map(([k, v]) => `${k}: ${v.length}`);
1977
+ const meta = [...numbers, ...lists].join(", ");
1978
+ return `- ${clip(label, 160, state)}${uid && uid !== label ? ` [${uid}]` : ""}${meta ? ` (${meta})` : ""}`;
1979
+ });
1980
+ if (items.length > 0) {
1981
+ sections.push({ heading: `## ${key.replace(/_/g, " ")}`, items });
1982
+ }
1983
+ }
1984
+ if (sections.length === 0) return void 0;
1985
+ return assemble("# Synthesis signals", sections, state);
1986
+ }
1987
+
1477
1988
  // src/tools.ts
1478
1989
  var TOOLS = [
1479
1990
  {
@@ -2048,10 +2559,16 @@ var TOOLS = [
2048
2559
  "neighborhood",
2049
2560
  "path",
2050
2561
  "subgraph",
2562
+ "top_k_paths",
2051
2563
  "document_index"
2052
2564
  ],
2053
2565
  description: "Retrieval or traversal strategy"
2054
2566
  },
2567
+ format: {
2568
+ type: "string",
2569
+ enum: ["text", "json"],
2570
+ 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)."
2571
+ },
2055
2572
  query: {
2056
2573
  type: "string",
2057
2574
  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 +2628,35 @@ var TOOLS = [
2111
2628
  },
2112
2629
  include_documents: {
2113
2630
  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."
2631
+ 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."
2632
+ },
2633
+ article_limit: {
2634
+ type: "number",
2635
+ 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."
2636
+ },
2637
+ valid_at: {
2638
+ type: "string",
2639
+ description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
2640
+ },
2641
+ created_after: {
2642
+ type: "number",
2643
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
2644
+ },
2645
+ created_before: {
2646
+ type: "number",
2647
+ description: "Unix seconds \u2014 'recent' action: only nodes ingested before this time"
2648
+ },
2649
+ explain: {
2650
+ type: "boolean",
2651
+ description: "hybrid action: include per-leg retrieval scoring (legs) in the response"
2652
+ },
2653
+ k: {
2654
+ type: "number",
2655
+ description: "top_k_paths action: number of cheapest paths (default 3, cap 25)"
2656
+ },
2657
+ max_cost: {
2658
+ type: "number",
2659
+ description: "top_k_paths action: optional cost ceiling for returned paths"
2115
2660
  },
2116
2661
  include_chunks: {
2117
2662
  type: "boolean",
@@ -2248,6 +2793,11 @@ var TOOLS = [
2248
2793
  type: "string",
2249
2794
  description: "Job ID to check status (for job_status action)"
2250
2795
  },
2796
+ format: {
2797
+ type: "string",
2798
+ enum: ["text", "json"],
2799
+ description: "job_status without job_id: 'text' (default) renders the 20 most recent jobs; 'json' returns the full raw list."
2800
+ },
2251
2801
  agent_id: {
2252
2802
  type: "string",
2253
2803
  description: "Agent identity"
@@ -2283,6 +2833,11 @@ var TOOLS = [
2283
2833
  type: "string",
2284
2834
  description: "Job ID to poll (for action='job_status')"
2285
2835
  },
2836
+ format: {
2837
+ type: "string",
2838
+ enum: ["text", "json"],
2839
+ description: "signals action: 'text' (default) renders signal sections; 'json' returns the raw payload."
2840
+ },
2286
2841
  agent_id: {
2287
2842
  type: "string",
2288
2843
  description: "Agent identity"
@@ -2391,6 +2946,11 @@ var TOOLS = [
2391
2946
  items: { type: "string" },
2392
2947
  description: "Chunk/document UIDs to extract from (for action=extract)"
2393
2948
  },
2949
+ format: {
2950
+ type: "string",
2951
+ enum: ["text", "json"],
2952
+ description: "Output format for the read actions query/search/objects/object/object_context (default 'text'): a compact rendered block. Pass 'json' for the raw server response."
2953
+ },
2394
2954
  agent_id: {
2395
2955
  type: "string",
2396
2956
  description: "Agent identity"
@@ -2433,9 +2993,14 @@ function toolsForProfile(profile = process.env.MINDGRAPH_PROFILE || "general") {
2433
2993
  }
2434
2994
  function ok3(data) {
2435
2995
  return {
2436
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
2996
+ content: [{ type: "text", text: JSON.stringify(data) }]
2437
2997
  };
2438
2998
  }
2999
+ function renderedResult(format, raw, render) {
3000
+ if (format === "json") return ok3(raw);
3001
+ const text = render(raw);
3002
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
3003
+ }
2439
3004
  function err3(message) {
2440
3005
  return {
2441
3006
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
@@ -3017,6 +3582,7 @@ async function handlePlan(client2, args) {
3017
3582
  async function handleRetrieve(client2, args) {
3018
3583
  const {
3019
3584
  action,
3585
+ format,
3020
3586
  query,
3021
3587
  start_uid,
3022
3588
  end_uid,
@@ -3037,12 +3603,20 @@ async function handleRetrieve(client2, args) {
3037
3603
  graph_max_depth,
3038
3604
  confidence_min,
3039
3605
  salience_min,
3606
+ article_limit,
3607
+ valid_at,
3608
+ created_after,
3609
+ created_before,
3610
+ explain,
3611
+ k,
3612
+ max_cost,
3040
3613
  agent_id
3041
3614
  } = args;
3615
+ const rendered2 = (raw, render) => renderedResult(format, raw, render);
3042
3616
  switch (action) {
3043
3617
  case "context":
3044
3618
  if (!query) return err3("query is required for context retrieval");
3045
- return ok3(
3619
+ return rendered2(
3046
3620
  await client2.retrieveContext({
3047
3621
  query,
3048
3622
  node_limit: limit,
@@ -3051,110 +3625,139 @@ async function handleRetrieve(client2, args) {
3051
3625
  chunk_limit: include_chunks ? limit ?? 5 : 0,
3052
3626
  // Scoped by default, never walled: the coding profile drops the
3053
3627
  // 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 } : {},
3628
+ // model opts in for a genuinely document-shaped question. Any
3629
+ // profile can drop it with include_documents: false (previously a
3630
+ // silent no-op outside coding). An EXPLICIT article_limit is at
3631
+ // least as clear an opt-in as include_documents:true — it always
3632
+ // wins over profile defaults (a coding-profile model reaching for
3633
+ // the advertised knob must not get a silent no-op).
3634
+ ...article_limit !== void 0 ? { article_limit } : include_documents === false || process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
3635
+ valid_at,
3056
3636
  include_graph,
3057
3637
  graph_expansion_limit: graph_expansion_limit ?? 3,
3058
3638
  graph_max_depth: graph_max_depth ?? 2
3059
- })
3639
+ }),
3640
+ renderContext
3060
3641
  );
3061
3642
  case "text":
3062
3643
  if (!query) return err3("query is required for text search");
3063
- return ok3(
3644
+ return rendered2(
3064
3645
  await client2.retrieve({
3065
3646
  action: "text",
3066
3647
  query,
3067
3648
  limit,
3068
3649
  node_types,
3069
3650
  layer,
3651
+ explain,
3070
3652
  agent_id
3071
- })
3653
+ }),
3654
+ (raw) => renderNodeList(raw, `Text search: ${query}`)
3072
3655
  );
3073
3656
  case "semantic":
3074
3657
  if (!query) return err3("query is required for semantic search");
3075
- return ok3(
3658
+ return rendered2(
3076
3659
  await client2.retrieve({
3077
3660
  action: "semantic",
3078
3661
  query,
3079
3662
  k: limit,
3080
3663
  node_types,
3081
3664
  layer,
3665
+ explain,
3082
3666
  agent_id
3083
- })
3667
+ }),
3668
+ (raw) => renderNodeList(raw, `Semantic search: ${query}`)
3084
3669
  );
3085
3670
  case "hybrid":
3086
3671
  if (!query) return err3("query is required for hybrid search");
3087
- return ok3(
3672
+ return rendered2(
3088
3673
  await client2.retrieve({
3089
3674
  action: "hybrid",
3090
3675
  query,
3091
3676
  k: limit,
3092
3677
  node_types,
3093
3678
  layer,
3679
+ explain,
3094
3680
  agent_id
3095
- })
3681
+ }),
3682
+ // With explain, per-leg scoring must survive — raw JSON only.
3683
+ explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
3096
3684
  );
3097
- // Structured queries
3685
+ // Structured queries. These go through the generic /retrieve action
3686
+ // dispatch rather than the SDK's no-argument convenience methods — the
3687
+ // conveniences silently dropped `limit`/`offset` (the tool advertised
3688
+ // them; five actions ignored them).
3098
3689
  case "active_goals":
3099
- return ok3(await client2.getGoals());
3100
3690
  case "open_questions":
3101
- return ok3(await client2.getOpenQuestions());
3102
3691
  case "weak_claims":
3103
- return ok3(await client2.getWeakClaims());
3104
3692
  case "pending_approvals":
3105
- return ok3(await client2.getPendingApprovals());
3106
3693
  case "unresolved_contradictions":
3107
- return ok3(await client2.getContradictions());
3694
+ return rendered2(
3695
+ await client2.retrieve({
3696
+ action,
3697
+ limit,
3698
+ offset,
3699
+ agent_id
3700
+ }),
3701
+ (raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
3702
+ );
3108
3703
  case "stale_derivations":
3109
- return ok3(
3704
+ return rendered2(
3110
3705
  await client2.retrieve({
3111
3706
  action: "stale_derivations",
3112
3707
  limit,
3113
3708
  offset
3114
- })
3709
+ }),
3710
+ (raw) => renderNodeList(raw, "stale derivations", limit)
3115
3711
  );
3116
3712
  case "preferences":
3117
- return ok3(
3713
+ return rendered2(
3118
3714
  await client2.retrieve({
3119
3715
  action: "preferences",
3120
3716
  ...query ? { query, k: limit } : {},
3121
3717
  limit,
3122
3718
  agent_id
3123
- })
3719
+ }),
3720
+ (raw) => renderNodeList(raw, "Preferences", limit)
3124
3721
  );
3125
3722
  case "layer":
3126
3723
  if (!layer) return err3("layer is required for layer retrieval");
3127
- return ok3(
3724
+ return rendered2(
3128
3725
  await client2.retrieve({
3129
3726
  action: "layer",
3130
3727
  layer,
3131
3728
  limit,
3729
+ node_types,
3132
3730
  agent_id
3133
- })
3731
+ }),
3732
+ (raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
3134
3733
  );
3135
3734
  case "recent":
3136
- return ok3(
3735
+ return rendered2(
3137
3736
  await client2.retrieve({
3138
3737
  action: "recent",
3139
3738
  limit,
3140
3739
  node_types,
3141
3740
  confidence_min,
3142
3741
  salience_min,
3742
+ created_after,
3743
+ created_before,
3143
3744
  agent_id
3144
- })
3745
+ }),
3746
+ (raw) => renderNodeList(raw, "Recently ingested", limit)
3145
3747
  );
3146
3748
  // Document inventory
3147
3749
  case "document_index":
3148
- return ok3(
3750
+ return rendered2(
3149
3751
  await client2.getNodes({
3150
3752
  node_type: "Document",
3151
3753
  limit: limit ?? 100
3152
- })
3754
+ }),
3755
+ (raw) => renderNodeList(raw, "Ingested documents")
3153
3756
  );
3154
3757
  // Graph traversal
3155
3758
  case "chain":
3156
3759
  if (!start_uid) return err3("start_uid is required for chain traversal");
3157
- return ok3(
3760
+ return rendered2(
3158
3761
  await client2.traverse({
3159
3762
  action: "chain",
3160
3763
  start_uid,
@@ -3162,11 +3765,12 @@ async function handleRetrieve(client2, args) {
3162
3765
  exclude_edge_types,
3163
3766
  include_provenance,
3164
3767
  max_nodes
3165
- })
3768
+ }),
3769
+ renderTraversal
3166
3770
  );
3167
3771
  case "neighborhood":
3168
3772
  if (!start_uid) return err3("start_uid is required for neighborhood traversal");
3169
- return ok3(
3773
+ return rendered2(
3170
3774
  await client2.traverse({
3171
3775
  action: "neighborhood",
3172
3776
  start_uid,
@@ -3176,12 +3780,13 @@ async function handleRetrieve(client2, args) {
3176
3780
  exclude_edge_types,
3177
3781
  include_provenance,
3178
3782
  max_nodes
3179
- })
3783
+ }),
3784
+ renderTraversal
3180
3785
  );
3181
3786
  case "path":
3182
3787
  if (!start_uid) return err3("start_uid is required for path traversal");
3183
3788
  if (!end_uid) return err3("end_uid is required for path traversal");
3184
- return ok3(
3789
+ return rendered2(
3185
3790
  await client2.traverse({
3186
3791
  action: "path",
3187
3792
  start_uid,
@@ -3192,11 +3797,12 @@ async function handleRetrieve(client2, args) {
3192
3797
  exclude_edge_types,
3193
3798
  include_provenance,
3194
3799
  max_nodes
3195
- })
3800
+ }),
3801
+ renderTraversal
3196
3802
  );
3197
3803
  case "subgraph":
3198
3804
  if (!start_uid) return err3("start_uid is required for subgraph traversal");
3199
- return ok3(
3805
+ return rendered2(
3200
3806
  await client2.traverse({
3201
3807
  action: "subgraph",
3202
3808
  start_uid,
@@ -3206,7 +3812,24 @@ async function handleRetrieve(client2, args) {
3206
3812
  exclude_edge_types,
3207
3813
  include_provenance,
3208
3814
  max_nodes
3209
- })
3815
+ }),
3816
+ renderTraversal
3817
+ );
3818
+ case "top_k_paths":
3819
+ if (!start_uid) return err3("start_uid is required for top_k_paths");
3820
+ if (!end_uid) return err3("end_uid is required for top_k_paths");
3821
+ return rendered2(
3822
+ await client2.traverse({
3823
+ action: "top_k_paths",
3824
+ start_uid,
3825
+ end_uid,
3826
+ k,
3827
+ max_hops: max_depth,
3828
+ max_cost,
3829
+ edge_types,
3830
+ exclude_edge_types
3831
+ }),
3832
+ renderTraversal
3210
3833
  );
3211
3834
  default:
3212
3835
  return err3(`Unknown retrieve action: ${action}`);
@@ -3232,6 +3855,7 @@ async function handleIngest(client2, args) {
3232
3855
  chunk_size,
3233
3856
  chunk_overlap,
3234
3857
  job_id,
3858
+ format,
3235
3859
  agent_id
3236
3860
  } = args;
3237
3861
  const src = source_uri ?? source;
@@ -3301,19 +3925,21 @@ async function handleIngest(client2, args) {
3301
3925
  if (job_id) {
3302
3926
  return ok3(await client2.getJob(job_id));
3303
3927
  }
3304
- return ok3(await client2.listJobs());
3928
+ return renderedResult(format, await client2.listJobs(), renderJobs);
3305
3929
  default:
3306
3930
  return err3(`Unknown ingest action: ${action}`);
3307
3931
  }
3308
3932
  }
3309
3933
  async function handleSynthesize(client2, args) {
3310
- const { action, project_uid, signals, target_types, job_id } = args;
3934
+ const { action, project_uid, signals, target_types, job_id, format } = args;
3311
3935
  switch (action) {
3312
3936
  case "signals":
3313
3937
  if (!project_uid)
3314
3938
  return err3("project_uid is required for action='signals'");
3315
- return ok3(
3316
- await client2.signals(project_uid, { signals, target_types })
3939
+ return renderedResult(
3940
+ format,
3941
+ await client2.signals(project_uid, { signals, target_types }),
3942
+ renderSignals
3317
3943
  );
3318
3944
  case "run":
3319
3945
  if (!project_uid)
@@ -3347,6 +3973,7 @@ async function handleOntology(client2, args) {
3347
3973
  relation_type,
3348
3974
  fields,
3349
3975
  source_uids,
3976
+ format,
3350
3977
  agent_id
3351
3978
  } = args;
3352
3979
  switch (action) {
@@ -3362,7 +3989,8 @@ async function handleOntology(client2, args) {
3362
3989
  "schema_id is required for action='query' (use action='schemas' to find the active schema)"
3363
3990
  );
3364
3991
  }
3365
- return ok3(
3992
+ return renderedResult(
3993
+ format,
3366
3994
  await client2.queryOntology({
3367
3995
  query,
3368
3996
  schema_id,
@@ -3371,29 +3999,42 @@ async function handleOntology(client2, args) {
3371
3999
  include_sources: include_sources ?? true,
3372
4000
  depth,
3373
4001
  limit
3374
- })
4002
+ }),
4003
+ renderOntologyAnswer
3375
4004
  );
3376
4005
  }
3377
4006
  case "search":
3378
4007
  if (!query) return err3("query is required for action='search'");
3379
- return ok3(
4008
+ return renderedResult(
4009
+ format,
3380
4010
  await client2.searchDomainObjects(query, {
3381
4011
  schema_id,
3382
4012
  object_types,
3383
4013
  limit
3384
- })
4014
+ }),
4015
+ (raw) => renderObjectList(raw, `Domain search: ${query}`)
3385
4016
  );
3386
4017
  case "objects":
3387
4018
  if (!schema_id) return err3("schema_id is required for action='objects'");
3388
- return ok3(
3389
- await client2.listDomainObjects({ schema_id, object_type, limit })
4019
+ return renderedResult(
4020
+ format,
4021
+ await client2.listDomainObjects({ schema_id, object_type, limit }),
4022
+ (raw) => renderObjectList(raw, `Domain objects${object_type ? `: ${object_type}` : ""}`)
3390
4023
  );
3391
4024
  case "object":
3392
4025
  if (!uid) return err3("uid is required for action='object'");
3393
- return ok3(await client2.getDomainObject(uid));
4026
+ return renderedResult(
4027
+ format,
4028
+ await client2.getDomainObject(uid),
4029
+ (raw) => renderNodeList([raw], "Domain object")
4030
+ );
3394
4031
  case "object_context":
3395
4032
  if (!uid) return err3("uid is required for action='object_context'");
3396
- return ok3(await client2.getDomainObjectContext(uid, depth));
4033
+ return renderedResult(
4034
+ format,
4035
+ await client2.getDomainObjectContext(uid, depth),
4036
+ renderOntologyAnswer
4037
+ );
3397
4038
  case "proposals":
3398
4039
  return ok3(
3399
4040
  await client2.listOntologyProposals({
@@ -3447,8 +4088,14 @@ async function handleOntology(client2, args) {
3447
4088
 
3448
4089
  // src/generated-tools.ts
3449
4090
  var ok4 = (data) => ({
3450
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
4091
+ // Compact on purpose — pretty-printing doubled the token cost.
4092
+ content: [{ type: "text", text: JSON.stringify(data) }]
3451
4093
  });
4094
+ var rendered = (format, raw, render) => {
4095
+ if (format === "json") return ok4(raw);
4096
+ const text = render(raw);
4097
+ return text !== void 0 ? { content: [{ type: "text", text }] } : ok4(raw);
4098
+ };
3452
4099
  var err4 = (message) => ({
3453
4100
  content: [{ type: "text", text: JSON.stringify({ error: message }) }],
3454
4101
  isError: true
@@ -3487,10 +4134,21 @@ async function getGeneratedTools(client2) {
3487
4134
  );
3488
4135
  }
3489
4136
  byName.set(d.name, d);
4137
+ const inputSchema = d.maps_to === "structured_query" ? d.input_schema : {
4138
+ ...d.input_schema,
4139
+ properties: {
4140
+ ...d.input_schema.properties,
4141
+ format: {
4142
+ type: "string",
4143
+ enum: ["text", "json"],
4144
+ description: "Output format (default 'text': compact rendered block). Pass 'json' for the raw server response."
4145
+ }
4146
+ }
4147
+ };
3490
4148
  tools.push({
3491
4149
  name: d.name,
3492
4150
  description: d.description,
3493
- inputSchema: d.input_schema
4151
+ inputSchema
3494
4152
  });
3495
4153
  }
3496
4154
  cache = { tools, byName, fetchedAt: Date.now() };
@@ -3519,31 +4177,41 @@ async function handleGeneratedTool(client2, desc, args) {
3519
4177
  if (!query) return err4(`query is required for ${desc.name}`);
3520
4178
  const limit = typeof args.limit === "number" ? args.limit : void 0;
3521
4179
  const filters = Array.isArray(args.filters) ? args.filters : void 0;
3522
- return ok4(
4180
+ return rendered(
4181
+ args.format,
3523
4182
  await ontologyClient.searchDomainObjects(query, {
3524
4183
  schema_id: desc.schema_id,
3525
4184
  object_types: [desc.object_type],
3526
4185
  filters,
3527
4186
  limit
3528
- })
4187
+ }),
4188
+ (raw) => renderObjectList(raw, `${desc.object_type} search: ${query}`)
3529
4189
  );
3530
4190
  }
3531
4191
  case "object": {
3532
4192
  const uid = args.uid;
3533
4193
  if (!uid) return err4(`uid is required for ${desc.name}`);
3534
- return ok4(await ontologyClient.getDomainObject(uid, {
3535
- schema_id: desc.schema_id,
3536
- object_type: desc.object_type
3537
- }));
4194
+ return rendered(
4195
+ args.format,
4196
+ await ontologyClient.getDomainObject(uid, {
4197
+ schema_id: desc.schema_id,
4198
+ object_type: desc.object_type
4199
+ }),
4200
+ (raw) => renderNodeList([raw], desc.object_type)
4201
+ );
3538
4202
  }
3539
4203
  case "object_context": {
3540
4204
  const uid = args.uid;
3541
4205
  if (!uid) return err4(`uid is required for ${desc.name}`);
3542
4206
  const depth = typeof args.depth === "number" ? args.depth : void 0;
3543
- return ok4(await ontologyClient.getDomainObjectContext(uid, depth, {
3544
- schema_id: desc.schema_id,
3545
- object_type: desc.object_type
3546
- }));
4207
+ return rendered(
4208
+ args.format,
4209
+ await ontologyClient.getDomainObjectContext(uid, depth, {
4210
+ schema_id: desc.schema_id,
4211
+ object_type: desc.object_type
4212
+ }),
4213
+ renderOntologyAnswer
4214
+ );
3547
4215
  }
3548
4216
  case "related": {
3549
4217
  const uid = args.uid;
@@ -3556,18 +4224,22 @@ async function handleGeneratedTool(client2, desc, args) {
3556
4224
  "related ontology tools require a graph-aware version of the mindgraph SDK"
3557
4225
  );
3558
4226
  }
3559
- return ok4(await ontologyClient.queryRelatedDomainObjects({
3560
- schema_id: desc.schema_id,
3561
- entry_type: desc.object_type,
3562
- uid,
3563
- relation: desc.relation,
3564
- entry_role: desc.entry_role,
3565
- far_type: desc.far_type,
3566
- where: Array.isArray(args.where) ? args.where : void 0,
3567
- include_provenance: args.include_provenance === true,
3568
- limit: typeof args.limit === "number" ? args.limit : void 0,
3569
- offset: typeof args.offset === "number" ? args.offset : void 0
3570
- }));
4227
+ return rendered(
4228
+ args.format,
4229
+ await ontologyClient.queryRelatedDomainObjects({
4230
+ schema_id: desc.schema_id,
4231
+ entry_type: desc.object_type,
4232
+ uid,
4233
+ relation: desc.relation,
4234
+ entry_role: desc.entry_role,
4235
+ far_type: desc.far_type,
4236
+ where: Array.isArray(args.where) ? args.where : void 0,
4237
+ include_provenance: args.include_provenance === true,
4238
+ limit: typeof args.limit === "number" ? args.limit : void 0,
4239
+ offset: typeof args.offset === "number" ? args.offset : void 0
4240
+ }),
4241
+ (raw) => renderObjectList(raw, `${desc.far_type} related via ${desc.relation}`)
4242
+ );
3571
4243
  }
3572
4244
  case "structured_query": {
3573
4245
  const select = args.select;
@@ -3886,7 +4558,7 @@ var RESOURCE_TEMPLATES = [
3886
4558
  var RESOURCE_GOVERNANCE_TOOL = "mindgraph_retrieve";
3887
4559
  function json(uri, data) {
3888
4560
  return {
3889
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
4561
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data) }]
3890
4562
  };
3891
4563
  }
3892
4564
  async function readResource(client2, uri) {
@@ -4059,7 +4731,7 @@ if (PROFILE === "coding") {
4059
4731
  var server = new import_server.Server(
4060
4732
  {
4061
4733
  name: "mindgraph",
4062
- version: "0.15.0"
4734
+ version: "0.17.0"
4063
4735
  },
4064
4736
  {
4065
4737
  capabilities: {
@@ -4273,7 +4945,7 @@ async function main() {
4273
4945
  const transport = new import_stdio.StdioServerTransport();
4274
4946
  await server.connect(transport);
4275
4947
  console.error(
4276
- `MindGraph MCP server v${"0.15.0"} running on stdio (${BASE_URL})`
4948
+ `MindGraph MCP server v${"0.17.0"} running on stdio (${BASE_URL})`
4277
4949
  );
4278
4950
  }
4279
4951
  main().catch((error) => {