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/README.md +5 -5
- package/dist/cli.js +761 -81
- package/dist/index.js +750 -78
- package/package.json +1 -1
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
|
|
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 }
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3316
|
-
|
|
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
|
|
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
|
|
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
|
|
3389
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
3535
|
-
|
|
3536
|
-
|
|
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
|
|
3544
|
-
|
|
3545
|
-
|
|
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
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
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
|
|
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.
|
|
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.
|
|
4948
|
+
`MindGraph MCP server v${"0.17.0"} running on stdio (${BASE_URL})`
|
|
4277
4949
|
);
|
|
4278
4950
|
}
|
|
4279
4951
|
main().catch((error) => {
|