mindgraph-mcp 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +489 -37
- package/dist/index.js +482 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1474,6 +1474,355 @@ async function scanRepository(remote, repository, requested, provider, agentId)
|
|
|
1474
1474
|
};
|
|
1475
1475
|
}
|
|
1476
1476
|
|
|
1477
|
+
// src/render.ts
|
|
1478
|
+
var LABEL_MAX = 200;
|
|
1479
|
+
var SUMMARY_MAX = 400;
|
|
1480
|
+
var QUOTE_MAX = 180;
|
|
1481
|
+
var ARTICLE_MAX = 6e3;
|
|
1482
|
+
var CHUNK_MAX = 6e3;
|
|
1483
|
+
var RELATIONSHIP_MAX = 40;
|
|
1484
|
+
var RENDER_CHAR_BUDGET = 24e3;
|
|
1485
|
+
var CURATION_EDGE_TYPES = /* @__PURE__ */ new Set(["POSSIBLE_DUPLICATE", "PossibleDuplicate"]);
|
|
1486
|
+
var INFRA_NODE_TYPES = /* @__PURE__ */ new Set(["Chunk", "Document", "Article"]);
|
|
1487
|
+
function obj(value) {
|
|
1488
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1489
|
+
}
|
|
1490
|
+
function str(value) {
|
|
1491
|
+
return typeof value === "string" && value ? value : void 0;
|
|
1492
|
+
}
|
|
1493
|
+
function num(value) {
|
|
1494
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1495
|
+
}
|
|
1496
|
+
function clip(value, max, state) {
|
|
1497
|
+
const text = str(value);
|
|
1498
|
+
if (!text) return void 0;
|
|
1499
|
+
if (text.length <= max) return text;
|
|
1500
|
+
state.bounded = true;
|
|
1501
|
+
return `${text.slice(0, max - 1)}\u2026`;
|
|
1502
|
+
}
|
|
1503
|
+
function isNodeShaped(value) {
|
|
1504
|
+
const node = obj(value);
|
|
1505
|
+
return Boolean(node && str(node.uid) && (str(node.label) || str(node.node_type)));
|
|
1506
|
+
}
|
|
1507
|
+
function unwrapSearchResult(value) {
|
|
1508
|
+
if (isNodeShaped(value)) return value;
|
|
1509
|
+
const wrapper = obj(value);
|
|
1510
|
+
const inner = obj(wrapper?.node);
|
|
1511
|
+
if (inner && isNodeShaped(inner)) {
|
|
1512
|
+
const score = num(wrapper.score);
|
|
1513
|
+
return score !== void 0 ? { ...inner, retrieval_score: score } : inner;
|
|
1514
|
+
}
|
|
1515
|
+
return void 0;
|
|
1516
|
+
}
|
|
1517
|
+
function statusTag(node) {
|
|
1518
|
+
const tags = [];
|
|
1519
|
+
if (node.superseded === true) {
|
|
1520
|
+
tags.push(
|
|
1521
|
+
node.superseded_by ? `SUPERSEDED by ${str(node.superseded_by)}` : "SUPERSEDED"
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
const truth = str(node.truth_status) ?? str(obj(node.props)?.truth_status);
|
|
1525
|
+
if (truth && truth !== "accepted") tags.push(`truth: ${truth}`);
|
|
1526
|
+
if (node.currently_valid === false) tags.push("OUTSIDE ITS VALIDITY WINDOW");
|
|
1527
|
+
if (node.valid_at_time === false) tags.push("NOT VALID AT THE REQUESTED TIME");
|
|
1528
|
+
return tags.length > 0 ? ` [${tags.join("; ")}]` : "";
|
|
1529
|
+
}
|
|
1530
|
+
function firstQuote(node, state) {
|
|
1531
|
+
const chunks = Array.isArray(node.source_chunks) ? node.source_chunks : [];
|
|
1532
|
+
for (const raw of chunks) {
|
|
1533
|
+
const chunk = obj(raw);
|
|
1534
|
+
const quote = str(chunk?.quote) ?? str(obj(chunk?.anchor)?.exact);
|
|
1535
|
+
if (quote) return clip(quote, QUOTE_MAX, state);
|
|
1536
|
+
}
|
|
1537
|
+
return void 0;
|
|
1538
|
+
}
|
|
1539
|
+
function sourceTag(node) {
|
|
1540
|
+
const docs = Array.isArray(node.source_documents) ? node.source_documents : [];
|
|
1541
|
+
const first = obj(docs[0]);
|
|
1542
|
+
const title = str(first?.title);
|
|
1543
|
+
if (!title) return void 0;
|
|
1544
|
+
const extra = docs.length > 1 ? ` (+${docs.length - 1} more)` : "";
|
|
1545
|
+
return `from: ${title}${extra}`;
|
|
1546
|
+
}
|
|
1547
|
+
function layer7Fields(node, state) {
|
|
1548
|
+
const fields = obj(obj(node.props)?.fields);
|
|
1549
|
+
if (!fields) return void 0;
|
|
1550
|
+
const pairs = Object.entries(fields).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
|
|
1551
|
+
if (pairs.length === 0) return void 0;
|
|
1552
|
+
return clip(pairs.join("; "), SUMMARY_MAX, state);
|
|
1553
|
+
}
|
|
1554
|
+
function nodeSummaryText(node, state) {
|
|
1555
|
+
const props = obj(node.props);
|
|
1556
|
+
return clip(node.summary, SUMMARY_MAX, state) ?? clip(props?.content, SUMMARY_MAX, state) ?? clip(props?.description, SUMMARY_MAX, state) ?? clip(props?.definition, SUMMARY_MAX, state);
|
|
1557
|
+
}
|
|
1558
|
+
function nodeLines(node, state) {
|
|
1559
|
+
const label = clip(node.label, LABEL_MAX, state) || "(unlabeled)";
|
|
1560
|
+
const type = str(node.node_type) || "Node";
|
|
1561
|
+
const confidence = num(node.confidence);
|
|
1562
|
+
const score = num(node.retrieval_score);
|
|
1563
|
+
const parenthetical = [
|
|
1564
|
+
type,
|
|
1565
|
+
confidence !== void 0 ? `confidence ${confidence}` : void 0,
|
|
1566
|
+
score !== void 0 ? `score ${Math.round(score * 100) / 100}` : void 0
|
|
1567
|
+
].filter(Boolean).join(", ");
|
|
1568
|
+
const head = `- **${label}** [${str(node.uid) || "?"}] (${parenthetical})${statusTag(node)}`;
|
|
1569
|
+
const summary = nodeSummaryText(node, state);
|
|
1570
|
+
const lines = [summary && summary !== label ? `${head}: ${summary}` : head];
|
|
1571
|
+
const fields = layer7Fields(node, state);
|
|
1572
|
+
if (fields) lines.push(` - fields: ${fields}`);
|
|
1573
|
+
const quote = firstQuote(node, state);
|
|
1574
|
+
if (quote) lines.push(` - source quote: "${quote}"`);
|
|
1575
|
+
const source = sourceTag(node);
|
|
1576
|
+
if (source) lines.push(` - ${source}`);
|
|
1577
|
+
const believers = Array.isArray(node.believed_by) ? node.believed_by : [];
|
|
1578
|
+
if (believers.length > 0) {
|
|
1579
|
+
const rendered = believers.slice(0, 3).map((raw) => {
|
|
1580
|
+
const believer = obj(raw);
|
|
1581
|
+
const conf = num(believer?.confidence);
|
|
1582
|
+
return `${str(believer?.agent_label) || "?"}${conf !== void 0 ? ` (${conf})` : ""}`;
|
|
1583
|
+
}).join(", ");
|
|
1584
|
+
lines.push(` - believed by: ${rendered}`);
|
|
1585
|
+
}
|
|
1586
|
+
return lines.join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
function groupedNodeSections(nodes, state, headingPrefix = "") {
|
|
1589
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1590
|
+
for (const node of nodes) {
|
|
1591
|
+
const type = str(node.node_type) || "Node";
|
|
1592
|
+
const group = groups.get(type) ?? [];
|
|
1593
|
+
group.push(node);
|
|
1594
|
+
groups.set(type, group);
|
|
1595
|
+
}
|
|
1596
|
+
const sections = [];
|
|
1597
|
+
let first = true;
|
|
1598
|
+
for (const [type, group] of groups) {
|
|
1599
|
+
sections.push({
|
|
1600
|
+
heading: `${first ? headingPrefix : ""}### ${type}`,
|
|
1601
|
+
items: group.map((node) => nodeLines(node, state))
|
|
1602
|
+
});
|
|
1603
|
+
first = false;
|
|
1604
|
+
}
|
|
1605
|
+
return sections;
|
|
1606
|
+
}
|
|
1607
|
+
function relationshipItems(edges, labelByUid, state) {
|
|
1608
|
+
const lines = [];
|
|
1609
|
+
for (const raw of edges) {
|
|
1610
|
+
const edge = obj(raw);
|
|
1611
|
+
if (!edge) continue;
|
|
1612
|
+
const type = str(edge.edge_type) || "?";
|
|
1613
|
+
const from = str(edge.from_uid);
|
|
1614
|
+
const to = str(edge.to_uid);
|
|
1615
|
+
if (CURATION_EDGE_TYPES.has(type)) continue;
|
|
1616
|
+
if (!from || !to || from === to) continue;
|
|
1617
|
+
const fromLabel = labelByUid.get(from);
|
|
1618
|
+
const toLabel = labelByUid.get(to);
|
|
1619
|
+
if (!fromLabel || !toLabel) continue;
|
|
1620
|
+
lines.push(`- ${fromLabel} \u2014${type}\u2192 ${toLabel}`);
|
|
1621
|
+
}
|
|
1622
|
+
if (lines.length > RELATIONSHIP_MAX) {
|
|
1623
|
+
state.bounded = true;
|
|
1624
|
+
const extra = lines.length - RELATIONSHIP_MAX;
|
|
1625
|
+
return [...lines.slice(0, RELATIONSHIP_MAX), `- (+${extra} more relationships)`];
|
|
1626
|
+
}
|
|
1627
|
+
return lines;
|
|
1628
|
+
}
|
|
1629
|
+
function assemble(header, sections, state) {
|
|
1630
|
+
const lines = [header];
|
|
1631
|
+
let used = header.length;
|
|
1632
|
+
for (const section of sections) {
|
|
1633
|
+
if (section.items.length === 0 && !section.heading) continue;
|
|
1634
|
+
const headingCost = section.heading.length + 2;
|
|
1635
|
+
if (used + headingCost > RENDER_CHAR_BUDGET) {
|
|
1636
|
+
state.bounded = true;
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
let sectionUsed = 0;
|
|
1640
|
+
const kept = [];
|
|
1641
|
+
let dropped = 0;
|
|
1642
|
+
for (const item of section.items) {
|
|
1643
|
+
if (used + headingCost + sectionUsed + item.length + 1 > RENDER_CHAR_BUDGET) {
|
|
1644
|
+
dropped += 1;
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
kept.push(item);
|
|
1648
|
+
sectionUsed += item.length + 1;
|
|
1649
|
+
}
|
|
1650
|
+
if (kept.length === 0 && section.items.length > 0) {
|
|
1651
|
+
state.bounded = true;
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (dropped > 0) {
|
|
1655
|
+
state.bounded = true;
|
|
1656
|
+
const marker = `(+${dropped} more not shown)`;
|
|
1657
|
+
kept.push(marker);
|
|
1658
|
+
sectionUsed += marker.length + 1;
|
|
1659
|
+
}
|
|
1660
|
+
lines.push(
|
|
1661
|
+
section.items.length === 0 ? section.heading : `${section.heading}
|
|
1662
|
+
${kept.join("\n")}`
|
|
1663
|
+
);
|
|
1664
|
+
used += headingCost + sectionUsed;
|
|
1665
|
+
}
|
|
1666
|
+
if (state.bounded) {
|
|
1667
|
+
lines.push(
|
|
1668
|
+
'\u2026[rendered context bounded \u2014 fetch full node content by uid, or pass format: "json" for the raw response]'
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
return lines.join("\n\n");
|
|
1672
|
+
}
|
|
1673
|
+
function policySections(payload, state) {
|
|
1674
|
+
const policies = Array.isArray(payload.applicable_policies) ? payload.applicable_policies : [];
|
|
1675
|
+
if (policies.length === 0) return [];
|
|
1676
|
+
const items = policies.map((raw) => obj(raw)).filter((policy) => Boolean(policy)).map((policy) => {
|
|
1677
|
+
const rules = Array.isArray(policy.prose_rules) ? policy.prose_rules.map(String).join("; ") : policy.prose_rules;
|
|
1678
|
+
return `- ${str(policy.name) || "(unnamed policy)"}: ${clip(rules, SUMMARY_MAX, state) || "(structured rules apply)"}`;
|
|
1679
|
+
});
|
|
1680
|
+
return [{ heading: "## Applicable policies", items }];
|
|
1681
|
+
}
|
|
1682
|
+
function hiddenSections(payload) {
|
|
1683
|
+
const hidden = payload.hidden_item_count;
|
|
1684
|
+
if (hidden === void 0 || hidden === null) return [];
|
|
1685
|
+
return [
|
|
1686
|
+
{
|
|
1687
|
+
heading: `Note: ${hidden} item(s) were hidden by access scoping.`,
|
|
1688
|
+
items: []
|
|
1689
|
+
}
|
|
1690
|
+
];
|
|
1691
|
+
}
|
|
1692
|
+
function renderContext(response) {
|
|
1693
|
+
const payload = obj(response);
|
|
1694
|
+
if (!payload) return void 0;
|
|
1695
|
+
const graph = obj(payload.graph);
|
|
1696
|
+
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
1697
|
+
const shaped = rawNodes.filter(isNodeShaped);
|
|
1698
|
+
const nodes = shaped.filter((node) => !INFRA_NODE_TYPES.has(str(node.node_type) || ""));
|
|
1699
|
+
const infraOnly = shaped.length - nodes.length;
|
|
1700
|
+
const articles = Array.isArray(payload.articles) ? payload.articles : [];
|
|
1701
|
+
const chunks = Array.isArray(payload.chunks) ? payload.chunks : [];
|
|
1702
|
+
const state = { bounded: false };
|
|
1703
|
+
const sections = [];
|
|
1704
|
+
if (articles.length > 0) {
|
|
1705
|
+
sections.push({
|
|
1706
|
+
heading: "## Knowledge articles",
|
|
1707
|
+
items: articles.map((raw) => obj(raw)).filter((article) => Boolean(article)).map(
|
|
1708
|
+
(article) => `### ${str(article.label) || "(untitled)"} [${str(article.uid) || "?"}]
|
|
1709
|
+
${clip(article.content, ARTICLE_MAX, state) || ""}`
|
|
1710
|
+
)
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
if (nodes.length > 0) {
|
|
1714
|
+
sections.push(...groupedNodeSections(nodes, state, "## Knowledge graph\n\n"));
|
|
1715
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
1716
|
+
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
1717
|
+
const relationships = relationshipItems(
|
|
1718
|
+
Array.isArray(graph?.edges) ? graph.edges : [],
|
|
1719
|
+
labelByUid,
|
|
1720
|
+
state
|
|
1721
|
+
);
|
|
1722
|
+
if (relationships.length > 0) {
|
|
1723
|
+
sections.push({ heading: "## Relationships", items: relationships });
|
|
1724
|
+
}
|
|
1725
|
+
} else if (infraOnly > 0) {
|
|
1726
|
+
sections.push({
|
|
1727
|
+
heading: `Note: ${infraOnly} document/chunk node(s) matched \u2014 use action "document_index" or include_chunks for their content.`,
|
|
1728
|
+
items: []
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
if (chunks.length > 0) {
|
|
1732
|
+
sections.push({
|
|
1733
|
+
heading: "## Source excerpts",
|
|
1734
|
+
items: chunks.map((raw) => obj(raw)).filter((chunk) => Boolean(chunk)).map(
|
|
1735
|
+
(chunk) => `- [${str(chunk.chunk_uid) || "?"}] from "${str(chunk.document_title) || "unknown document"}": "${clip(chunk.content, CHUNK_MAX, state) || ""}"`
|
|
1736
|
+
)
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
sections.push(...policySections(payload, state));
|
|
1740
|
+
sections.push(...hiddenSections(payload));
|
|
1741
|
+
if (sections.length === 0) {
|
|
1742
|
+
return "No results found for this search.";
|
|
1743
|
+
}
|
|
1744
|
+
if (nodes.length === 0 && articles.length === 0 && chunks.length === 0) {
|
|
1745
|
+
return assemble("# Retrieved context\n\nNo graph results matched this search.", sections, state);
|
|
1746
|
+
}
|
|
1747
|
+
return assemble("# Retrieved context", sections, state);
|
|
1748
|
+
}
|
|
1749
|
+
function renderNodeList(response, title, limit) {
|
|
1750
|
+
let candidates = [];
|
|
1751
|
+
if (Array.isArray(response)) {
|
|
1752
|
+
candidates = response;
|
|
1753
|
+
} else {
|
|
1754
|
+
const payload = obj(response);
|
|
1755
|
+
if (!payload) return void 0;
|
|
1756
|
+
for (const key of ["nodes", "results", "items", "goals", "questions", "claims", "preferences", "documents"]) {
|
|
1757
|
+
const candidate = payload[key];
|
|
1758
|
+
if (Array.isArray(candidate) && candidate.some((entry) => unwrapSearchResult(entry))) {
|
|
1759
|
+
candidates = candidate;
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
let nodes = candidates.map(unwrapSearchResult).filter((node) => Boolean(node));
|
|
1765
|
+
if (nodes.length === 0) return void 0;
|
|
1766
|
+
const total = nodes.length;
|
|
1767
|
+
const state = { bounded: false };
|
|
1768
|
+
if (limit !== void 0 && limit > 0 && nodes.length > limit) {
|
|
1769
|
+
nodes = nodes.slice(0, limit);
|
|
1770
|
+
state.bounded = true;
|
|
1771
|
+
}
|
|
1772
|
+
const sections = groupedNodeSections(nodes, state);
|
|
1773
|
+
const shown = nodes.length;
|
|
1774
|
+
return assemble(
|
|
1775
|
+
`# ${title} (${shown === total ? total : `${shown} of ${total}`})`,
|
|
1776
|
+
sections,
|
|
1777
|
+
state
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
function renderTraversal(response) {
|
|
1781
|
+
const payload = obj(response);
|
|
1782
|
+
if (!payload) return void 0;
|
|
1783
|
+
const container = obj(payload.graph) ?? payload;
|
|
1784
|
+
const rawNodes = Array.isArray(container.nodes) ? container.nodes : [];
|
|
1785
|
+
const nodes = rawNodes.filter(isNodeShaped);
|
|
1786
|
+
const paths = Array.isArray(payload.paths) ? payload.paths : [];
|
|
1787
|
+
if (nodes.length === 0 && paths.length === 0) return void 0;
|
|
1788
|
+
const state = { bounded: false };
|
|
1789
|
+
const sections = [];
|
|
1790
|
+
const labelByUid = /* @__PURE__ */ new Map();
|
|
1791
|
+
for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
|
|
1792
|
+
if (paths.length > 0) {
|
|
1793
|
+
const items = paths.map((raw) => obj(raw)).filter((path3) => Boolean(path3)).map((path3, index) => {
|
|
1794
|
+
const uids = Array.isArray(path3.node_uids) ? path3.node_uids.map(String) : [];
|
|
1795
|
+
const labels = Array.isArray(path3.labels) ? path3.labels.map(String) : Array.isArray(path3.nodes) ? path3.nodes.map((hop) => {
|
|
1796
|
+
const hopNode = obj(hop);
|
|
1797
|
+
return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
|
|
1798
|
+
}) : [];
|
|
1799
|
+
const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
|
|
1800
|
+
const cost = num(path3.cost) ?? num(path3.path_cost);
|
|
1801
|
+
const confidence = num(path3.path_confidence);
|
|
1802
|
+
const meta = [
|
|
1803
|
+
cost !== void 0 ? `cost ${cost}` : void 0,
|
|
1804
|
+
confidence !== void 0 ? `confidence ${confidence}` : void 0
|
|
1805
|
+
].filter(Boolean);
|
|
1806
|
+
const head = `${index + 1}. ${route}${meta.length > 0 ? ` (${meta.join(", ")})` : ""}`;
|
|
1807
|
+
return labels.length > 0 && uids.length > 0 ? `${head}
|
|
1808
|
+
uids: ${uids.join(" \u2192 ")}` : head;
|
|
1809
|
+
});
|
|
1810
|
+
sections.push({ heading: "## Paths", items });
|
|
1811
|
+
}
|
|
1812
|
+
if (nodes.length > 0) {
|
|
1813
|
+
sections.push(...groupedNodeSections(nodes, state, "## Nodes\n\n"));
|
|
1814
|
+
const relationships = relationshipItems(
|
|
1815
|
+
Array.isArray(container.edges) ? container.edges : [],
|
|
1816
|
+
labelByUid,
|
|
1817
|
+
state
|
|
1818
|
+
);
|
|
1819
|
+
if (relationships.length > 0) {
|
|
1820
|
+
sections.push({ heading: "## Relationships", items: relationships });
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return assemble("# Graph traversal", sections, state);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1477
1826
|
// src/tools.ts
|
|
1478
1827
|
var TOOLS = [
|
|
1479
1828
|
{
|
|
@@ -2048,10 +2397,16 @@ var TOOLS = [
|
|
|
2048
2397
|
"neighborhood",
|
|
2049
2398
|
"path",
|
|
2050
2399
|
"subgraph",
|
|
2400
|
+
"top_k_paths",
|
|
2051
2401
|
"document_index"
|
|
2052
2402
|
],
|
|
2053
2403
|
description: "Retrieval or traversal strategy"
|
|
2054
2404
|
},
|
|
2405
|
+
format: {
|
|
2406
|
+
type: "string",
|
|
2407
|
+
enum: ["text", "json"],
|
|
2408
|
+
description: "Output format (default 'text'): a compact rendered context block \u2014 labels, uids, summaries, status tags, relationships. Pass 'json' for the raw server response (full props, source-chunk offsets, retrieval metadata)."
|
|
2409
|
+
},
|
|
2055
2410
|
query: {
|
|
2056
2411
|
type: "string",
|
|
2057
2412
|
description: "Search query \u2014 for 'text'/'context': 1-3 BM25 keywords, drop filler words. For 'semantic'/'hybrid': natural language is OK since it uses vector similarity."
|
|
@@ -2111,7 +2466,35 @@ var TOOLS = [
|
|
|
2111
2466
|
},
|
|
2112
2467
|
include_documents: {
|
|
2113
2468
|
type: "boolean",
|
|
2114
|
-
description: "Include ingested documents and wiki articles in context results. The coding profile defaults these OFF so engineering questions aren't answered from unrelated ingested content; pass true when the question is about a document, spec, or article."
|
|
2469
|
+
description: "Include ingested documents and wiki articles in context results. The coding profile defaults these OFF so engineering questions aren't answered from unrelated ingested content; pass true when the question is about a document, spec, or article \u2014 or false in any profile to drop the article leg."
|
|
2470
|
+
},
|
|
2471
|
+
article_limit: {
|
|
2472
|
+
type: "number",
|
|
2473
|
+
description: "Max wiki articles in context results (default 3; 0 disables the article leg). An explicit value overrides profile defaults, including the coding profile's articles-off default."
|
|
2474
|
+
},
|
|
2475
|
+
valid_at: {
|
|
2476
|
+
type: "string",
|
|
2477
|
+
description: "ISO-8601 date (e.g. '2021-06') for context: nodes with validity windows are annotated as of this date instead of today"
|
|
2478
|
+
},
|
|
2479
|
+
created_after: {
|
|
2480
|
+
type: "number",
|
|
2481
|
+
description: "Unix seconds \u2014 'recent' action: only nodes ingested after this time"
|
|
2482
|
+
},
|
|
2483
|
+
created_before: {
|
|
2484
|
+
type: "number",
|
|
2485
|
+
description: "Unix seconds \u2014 'recent' action: only nodes ingested before this time"
|
|
2486
|
+
},
|
|
2487
|
+
explain: {
|
|
2488
|
+
type: "boolean",
|
|
2489
|
+
description: "hybrid action: include per-leg retrieval scoring (legs) in the response"
|
|
2490
|
+
},
|
|
2491
|
+
k: {
|
|
2492
|
+
type: "number",
|
|
2493
|
+
description: "top_k_paths action: number of cheapest paths (default 3, cap 25)"
|
|
2494
|
+
},
|
|
2495
|
+
max_cost: {
|
|
2496
|
+
type: "number",
|
|
2497
|
+
description: "top_k_paths action: optional cost ceiling for returned paths"
|
|
2115
2498
|
},
|
|
2116
2499
|
include_chunks: {
|
|
2117
2500
|
type: "boolean",
|
|
@@ -3017,6 +3400,7 @@ async function handlePlan(client2, args) {
|
|
|
3017
3400
|
async function handleRetrieve(client2, args) {
|
|
3018
3401
|
const {
|
|
3019
3402
|
action,
|
|
3403
|
+
format,
|
|
3020
3404
|
query,
|
|
3021
3405
|
start_uid,
|
|
3022
3406
|
end_uid,
|
|
@@ -3037,12 +3421,24 @@ async function handleRetrieve(client2, args) {
|
|
|
3037
3421
|
graph_max_depth,
|
|
3038
3422
|
confidence_min,
|
|
3039
3423
|
salience_min,
|
|
3424
|
+
article_limit,
|
|
3425
|
+
valid_at,
|
|
3426
|
+
created_after,
|
|
3427
|
+
created_before,
|
|
3428
|
+
explain,
|
|
3429
|
+
k,
|
|
3430
|
+
max_cost,
|
|
3040
3431
|
agent_id
|
|
3041
3432
|
} = args;
|
|
3433
|
+
const rendered = (raw, render) => {
|
|
3434
|
+
if (format === "json") return ok3(raw);
|
|
3435
|
+
const text = render(raw);
|
|
3436
|
+
return text !== void 0 ? { content: [{ type: "text", text }] } : ok3(raw);
|
|
3437
|
+
};
|
|
3042
3438
|
switch (action) {
|
|
3043
3439
|
case "context":
|
|
3044
3440
|
if (!query) return err3("query is required for context retrieval");
|
|
3045
|
-
return
|
|
3441
|
+
return rendered(
|
|
3046
3442
|
await client2.retrieveContext({
|
|
3047
3443
|
query,
|
|
3048
3444
|
node_limit: limit,
|
|
@@ -3051,110 +3447,139 @@ async function handleRetrieve(client2, args) {
|
|
|
3051
3447
|
chunk_limit: include_chunks ? limit ?? 5 : 0,
|
|
3052
3448
|
// Scoped by default, never walled: the coding profile drops the
|
|
3053
3449
|
// document/article leg (the "Bob Work" class of noise) unless the
|
|
3054
|
-
// model opts in for a genuinely document-shaped question.
|
|
3055
|
-
|
|
3450
|
+
// model opts in for a genuinely document-shaped question. Any
|
|
3451
|
+
// profile can drop it with include_documents: false (previously a
|
|
3452
|
+
// silent no-op outside coding). An EXPLICIT article_limit is at
|
|
3453
|
+
// least as clear an opt-in as include_documents:true — it always
|
|
3454
|
+
// wins over profile defaults (a coding-profile model reaching for
|
|
3455
|
+
// the advertised knob must not get a silent no-op).
|
|
3456
|
+
...article_limit !== void 0 ? { article_limit } : include_documents === false || process.env.MINDGRAPH_PROFILE === "coding" && !include_documents ? { article_limit: 0 } : {},
|
|
3457
|
+
valid_at,
|
|
3056
3458
|
include_graph,
|
|
3057
3459
|
graph_expansion_limit: graph_expansion_limit ?? 3,
|
|
3058
3460
|
graph_max_depth: graph_max_depth ?? 2
|
|
3059
|
-
})
|
|
3461
|
+
}),
|
|
3462
|
+
renderContext
|
|
3060
3463
|
);
|
|
3061
3464
|
case "text":
|
|
3062
3465
|
if (!query) return err3("query is required for text search");
|
|
3063
|
-
return
|
|
3466
|
+
return rendered(
|
|
3064
3467
|
await client2.retrieve({
|
|
3065
3468
|
action: "text",
|
|
3066
3469
|
query,
|
|
3067
3470
|
limit,
|
|
3068
3471
|
node_types,
|
|
3069
3472
|
layer,
|
|
3473
|
+
explain,
|
|
3070
3474
|
agent_id
|
|
3071
|
-
})
|
|
3475
|
+
}),
|
|
3476
|
+
(raw) => renderNodeList(raw, `Text search: ${query}`)
|
|
3072
3477
|
);
|
|
3073
3478
|
case "semantic":
|
|
3074
3479
|
if (!query) return err3("query is required for semantic search");
|
|
3075
|
-
return
|
|
3480
|
+
return rendered(
|
|
3076
3481
|
await client2.retrieve({
|
|
3077
3482
|
action: "semantic",
|
|
3078
3483
|
query,
|
|
3079
3484
|
k: limit,
|
|
3080
3485
|
node_types,
|
|
3081
3486
|
layer,
|
|
3487
|
+
explain,
|
|
3082
3488
|
agent_id
|
|
3083
|
-
})
|
|
3489
|
+
}),
|
|
3490
|
+
(raw) => renderNodeList(raw, `Semantic search: ${query}`)
|
|
3084
3491
|
);
|
|
3085
3492
|
case "hybrid":
|
|
3086
3493
|
if (!query) return err3("query is required for hybrid search");
|
|
3087
|
-
return
|
|
3494
|
+
return rendered(
|
|
3088
3495
|
await client2.retrieve({
|
|
3089
3496
|
action: "hybrid",
|
|
3090
3497
|
query,
|
|
3091
3498
|
k: limit,
|
|
3092
3499
|
node_types,
|
|
3093
3500
|
layer,
|
|
3501
|
+
explain,
|
|
3094
3502
|
agent_id
|
|
3095
|
-
})
|
|
3503
|
+
}),
|
|
3504
|
+
// With explain, per-leg scoring must survive — raw JSON only.
|
|
3505
|
+
explain ? () => void 0 : (raw) => renderNodeList(raw, `Hybrid search: ${query}`)
|
|
3096
3506
|
);
|
|
3097
|
-
// Structured queries
|
|
3507
|
+
// Structured queries. These go through the generic /retrieve action
|
|
3508
|
+
// dispatch rather than the SDK's no-argument convenience methods — the
|
|
3509
|
+
// conveniences silently dropped `limit`/`offset` (the tool advertised
|
|
3510
|
+
// them; five actions ignored them).
|
|
3098
3511
|
case "active_goals":
|
|
3099
|
-
return ok3(await client2.getGoals());
|
|
3100
3512
|
case "open_questions":
|
|
3101
|
-
return ok3(await client2.getOpenQuestions());
|
|
3102
3513
|
case "weak_claims":
|
|
3103
|
-
return ok3(await client2.getWeakClaims());
|
|
3104
3514
|
case "pending_approvals":
|
|
3105
|
-
return ok3(await client2.getPendingApprovals());
|
|
3106
3515
|
case "unresolved_contradictions":
|
|
3107
|
-
return
|
|
3516
|
+
return rendered(
|
|
3517
|
+
await client2.retrieve({
|
|
3518
|
+
action,
|
|
3519
|
+
limit,
|
|
3520
|
+
offset,
|
|
3521
|
+
agent_id
|
|
3522
|
+
}),
|
|
3523
|
+
(raw) => renderNodeList(raw, action.replace(/_/g, " "), limit)
|
|
3524
|
+
);
|
|
3108
3525
|
case "stale_derivations":
|
|
3109
|
-
return
|
|
3526
|
+
return rendered(
|
|
3110
3527
|
await client2.retrieve({
|
|
3111
3528
|
action: "stale_derivations",
|
|
3112
3529
|
limit,
|
|
3113
3530
|
offset
|
|
3114
|
-
})
|
|
3531
|
+
}),
|
|
3532
|
+
(raw) => renderNodeList(raw, "stale derivations", limit)
|
|
3115
3533
|
);
|
|
3116
3534
|
case "preferences":
|
|
3117
|
-
return
|
|
3535
|
+
return rendered(
|
|
3118
3536
|
await client2.retrieve({
|
|
3119
3537
|
action: "preferences",
|
|
3120
3538
|
...query ? { query, k: limit } : {},
|
|
3121
3539
|
limit,
|
|
3122
3540
|
agent_id
|
|
3123
|
-
})
|
|
3541
|
+
}),
|
|
3542
|
+
(raw) => renderNodeList(raw, "Preferences", limit)
|
|
3124
3543
|
);
|
|
3125
3544
|
case "layer":
|
|
3126
3545
|
if (!layer) return err3("layer is required for layer retrieval");
|
|
3127
|
-
return
|
|
3546
|
+
return rendered(
|
|
3128
3547
|
await client2.retrieve({
|
|
3129
3548
|
action: "layer",
|
|
3130
3549
|
layer,
|
|
3131
3550
|
limit,
|
|
3551
|
+
node_types,
|
|
3132
3552
|
agent_id
|
|
3133
|
-
})
|
|
3553
|
+
}),
|
|
3554
|
+
(raw) => renderNodeList(raw, `Layer: ${layer}`, limit)
|
|
3134
3555
|
);
|
|
3135
3556
|
case "recent":
|
|
3136
|
-
return
|
|
3557
|
+
return rendered(
|
|
3137
3558
|
await client2.retrieve({
|
|
3138
3559
|
action: "recent",
|
|
3139
3560
|
limit,
|
|
3140
3561
|
node_types,
|
|
3141
3562
|
confidence_min,
|
|
3142
3563
|
salience_min,
|
|
3564
|
+
created_after,
|
|
3565
|
+
created_before,
|
|
3143
3566
|
agent_id
|
|
3144
|
-
})
|
|
3567
|
+
}),
|
|
3568
|
+
(raw) => renderNodeList(raw, "Recently ingested", limit)
|
|
3145
3569
|
);
|
|
3146
3570
|
// Document inventory
|
|
3147
3571
|
case "document_index":
|
|
3148
|
-
return
|
|
3572
|
+
return rendered(
|
|
3149
3573
|
await client2.getNodes({
|
|
3150
3574
|
node_type: "Document",
|
|
3151
3575
|
limit: limit ?? 100
|
|
3152
|
-
})
|
|
3576
|
+
}),
|
|
3577
|
+
(raw) => renderNodeList(raw, "Ingested documents")
|
|
3153
3578
|
);
|
|
3154
3579
|
// Graph traversal
|
|
3155
3580
|
case "chain":
|
|
3156
3581
|
if (!start_uid) return err3("start_uid is required for chain traversal");
|
|
3157
|
-
return
|
|
3582
|
+
return rendered(
|
|
3158
3583
|
await client2.traverse({
|
|
3159
3584
|
action: "chain",
|
|
3160
3585
|
start_uid,
|
|
@@ -3162,11 +3587,12 @@ async function handleRetrieve(client2, args) {
|
|
|
3162
3587
|
exclude_edge_types,
|
|
3163
3588
|
include_provenance,
|
|
3164
3589
|
max_nodes
|
|
3165
|
-
})
|
|
3590
|
+
}),
|
|
3591
|
+
renderTraversal
|
|
3166
3592
|
);
|
|
3167
3593
|
case "neighborhood":
|
|
3168
3594
|
if (!start_uid) return err3("start_uid is required for neighborhood traversal");
|
|
3169
|
-
return
|
|
3595
|
+
return rendered(
|
|
3170
3596
|
await client2.traverse({
|
|
3171
3597
|
action: "neighborhood",
|
|
3172
3598
|
start_uid,
|
|
@@ -3176,12 +3602,13 @@ async function handleRetrieve(client2, args) {
|
|
|
3176
3602
|
exclude_edge_types,
|
|
3177
3603
|
include_provenance,
|
|
3178
3604
|
max_nodes
|
|
3179
|
-
})
|
|
3605
|
+
}),
|
|
3606
|
+
renderTraversal
|
|
3180
3607
|
);
|
|
3181
3608
|
case "path":
|
|
3182
3609
|
if (!start_uid) return err3("start_uid is required for path traversal");
|
|
3183
3610
|
if (!end_uid) return err3("end_uid is required for path traversal");
|
|
3184
|
-
return
|
|
3611
|
+
return rendered(
|
|
3185
3612
|
await client2.traverse({
|
|
3186
3613
|
action: "path",
|
|
3187
3614
|
start_uid,
|
|
@@ -3192,11 +3619,12 @@ async function handleRetrieve(client2, args) {
|
|
|
3192
3619
|
exclude_edge_types,
|
|
3193
3620
|
include_provenance,
|
|
3194
3621
|
max_nodes
|
|
3195
|
-
})
|
|
3622
|
+
}),
|
|
3623
|
+
renderTraversal
|
|
3196
3624
|
);
|
|
3197
3625
|
case "subgraph":
|
|
3198
3626
|
if (!start_uid) return err3("start_uid is required for subgraph traversal");
|
|
3199
|
-
return
|
|
3627
|
+
return rendered(
|
|
3200
3628
|
await client2.traverse({
|
|
3201
3629
|
action: "subgraph",
|
|
3202
3630
|
start_uid,
|
|
@@ -3206,7 +3634,24 @@ async function handleRetrieve(client2, args) {
|
|
|
3206
3634
|
exclude_edge_types,
|
|
3207
3635
|
include_provenance,
|
|
3208
3636
|
max_nodes
|
|
3209
|
-
})
|
|
3637
|
+
}),
|
|
3638
|
+
renderTraversal
|
|
3639
|
+
);
|
|
3640
|
+
case "top_k_paths":
|
|
3641
|
+
if (!start_uid) return err3("start_uid is required for top_k_paths");
|
|
3642
|
+
if (!end_uid) return err3("end_uid is required for top_k_paths");
|
|
3643
|
+
return rendered(
|
|
3644
|
+
await client2.traverse({
|
|
3645
|
+
action: "top_k_paths",
|
|
3646
|
+
start_uid,
|
|
3647
|
+
end_uid,
|
|
3648
|
+
k,
|
|
3649
|
+
max_hops: max_depth,
|
|
3650
|
+
max_cost,
|
|
3651
|
+
edge_types,
|
|
3652
|
+
exclude_edge_types
|
|
3653
|
+
}),
|
|
3654
|
+
renderTraversal
|
|
3210
3655
|
);
|
|
3211
3656
|
default:
|
|
3212
3657
|
return err3(`Unknown retrieve action: ${action}`);
|
|
@@ -4059,7 +4504,7 @@ if (PROFILE === "coding") {
|
|
|
4059
4504
|
var server = new import_server.Server(
|
|
4060
4505
|
{
|
|
4061
4506
|
name: "mindgraph",
|
|
4062
|
-
version: "0.
|
|
4507
|
+
version: "0.16.0"
|
|
4063
4508
|
},
|
|
4064
4509
|
{
|
|
4065
4510
|
capabilities: {
|
|
@@ -4273,7 +4718,7 @@ async function main() {
|
|
|
4273
4718
|
const transport = new import_stdio.StdioServerTransport();
|
|
4274
4719
|
await server.connect(transport);
|
|
4275
4720
|
console.error(
|
|
4276
|
-
`MindGraph MCP server v${"0.
|
|
4721
|
+
`MindGraph MCP server v${"0.16.0"} running on stdio (${BASE_URL})`
|
|
4277
4722
|
);
|
|
4278
4723
|
}
|
|
4279
4724
|
main().catch((error) => {
|