@sage-protocol/pi-adapter 0.3.7 → 0.3.8
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 -3
- package/dist/sage-pi-adapter.d.ts +6 -1
- package/dist/sage-pi-adapter.d.ts.map +1 -1
- package/dist/sage-pi-adapter.js +381 -83
- package/dist/sage-pi-adapter.js.map +1 -1
- package/extensions/index.ts +1297 -1085
- package/package.json +1 -5
- package/skills/sage-bridge/SKILL.md +0 -327
package/dist/sage-pi-adapter.js
CHANGED
|
@@ -160,6 +160,7 @@ const DOMAINLESS_ACTION_DOMAIN_HINTS = {
|
|
|
160
160
|
};
|
|
161
161
|
const DEFAULT_SEARCH_ACTION_BY_DOMAIN = {
|
|
162
162
|
prompts: "list",
|
|
163
|
+
behaviors: "recommend",
|
|
163
164
|
skills: "list",
|
|
164
165
|
builder: "recommend",
|
|
165
166
|
governance: "list_subdaos",
|
|
@@ -170,6 +171,8 @@ const DEFAULT_SEARCH_ACTION_BY_DOMAIN = {
|
|
|
170
171
|
security: "scan",
|
|
171
172
|
meta: "get_project_context",
|
|
172
173
|
hub: "status",
|
|
174
|
+
activation: "active",
|
|
175
|
+
wallet: "balance",
|
|
173
176
|
library: "get_active",
|
|
174
177
|
help: "list",
|
|
175
178
|
external: "list_servers",
|
|
@@ -638,6 +641,53 @@ function formatOperationSubject(request) {
|
|
|
638
641
|
}
|
|
639
642
|
return `${request.domain}/${request.action}`;
|
|
640
643
|
}
|
|
644
|
+
function summarizeInventoryItem(value) {
|
|
645
|
+
if (typeof value === "string" && value.trim()) {
|
|
646
|
+
return truncateText(value.trim(), 96);
|
|
647
|
+
}
|
|
648
|
+
if (!value || typeof value !== "object")
|
|
649
|
+
return null;
|
|
650
|
+
const item = value;
|
|
651
|
+
const toolName = stringValue(item.tool_name);
|
|
652
|
+
const sessionId = stringValue(item.session_id);
|
|
653
|
+
const instanceId = stringValue(item.instance_id);
|
|
654
|
+
const serverId = stringValue(item.server_id) ?? stringValue(item.id) ?? instanceId;
|
|
655
|
+
if (toolName && serverId) {
|
|
656
|
+
return truncateText(`${toolName} @ ${serverId}`, 96);
|
|
657
|
+
}
|
|
658
|
+
if (instanceId) {
|
|
659
|
+
return truncateText(sessionId ? `${instanceId} (session ${sessionId})` : instanceId, 96);
|
|
660
|
+
}
|
|
661
|
+
const label = stringValue(item.title) ??
|
|
662
|
+
stringValue(item.name) ??
|
|
663
|
+
stringValue(item.key) ??
|
|
664
|
+
stringValue(item.id) ??
|
|
665
|
+
stringValue(item.url) ??
|
|
666
|
+
serverId;
|
|
667
|
+
if (!label)
|
|
668
|
+
return null;
|
|
669
|
+
const suffix = [];
|
|
670
|
+
if (typeof item.running === "boolean") {
|
|
671
|
+
suffix.push(item.running ? "running" : "stopped");
|
|
672
|
+
}
|
|
673
|
+
if (typeof item.enabled === "boolean") {
|
|
674
|
+
suffix.push(item.enabled ? "enabled" : "disabled");
|
|
675
|
+
}
|
|
676
|
+
if (typeof item.available === "boolean" && item.available === false) {
|
|
677
|
+
suffix.push("unavailable");
|
|
678
|
+
}
|
|
679
|
+
if (Array.isArray(item.tools)) {
|
|
680
|
+
suffix.push(`${item.tools.length} ${item.tools.length === 1 ? "tool" : "tools"}`);
|
|
681
|
+
}
|
|
682
|
+
else if (typeof item.tool_count === "number") {
|
|
683
|
+
suffix.push(`${item.tool_count} ${item.tool_count === 1 ? "tool" : "tools"}`);
|
|
684
|
+
}
|
|
685
|
+
const status = stringValue(item.status);
|
|
686
|
+
if (status && !suffix.includes(status))
|
|
687
|
+
suffix.push(status);
|
|
688
|
+
const decorated = suffix.length > 0 ? `${label} (${suffix.slice(0, 3).join(", ")})` : label;
|
|
689
|
+
return truncateText(decorated, 96);
|
|
690
|
+
}
|
|
641
691
|
function formatOperationDetails(request) {
|
|
642
692
|
const details = [];
|
|
643
693
|
if (request.domain === "external") {
|
|
@@ -659,6 +709,7 @@ function formatOperationDetails(request) {
|
|
|
659
709
|
const query = stringValue(request.params.query);
|
|
660
710
|
const key = stringValue(request.params.key) ?? stringValue(request.params.name);
|
|
661
711
|
const serverId = stringValue(request.params.server_id);
|
|
712
|
+
const sessionId = stringValue(request.params.session_id);
|
|
662
713
|
const library = stringValue(request.params.library);
|
|
663
714
|
if (query)
|
|
664
715
|
details.push(`query: ${quotedText(query)}`);
|
|
@@ -666,6 +717,8 @@ function formatOperationDetails(request) {
|
|
|
666
717
|
details.push(`key: ${truncateText(key, 84)}`);
|
|
667
718
|
if (serverId)
|
|
668
719
|
details.push(`server: ${serverId}`);
|
|
720
|
+
if (sessionId)
|
|
721
|
+
details.push(`session: ${truncateText(sessionId, 84)}`);
|
|
669
722
|
if (library)
|
|
670
723
|
details.push(`library: ${truncateText(library, 84)}`);
|
|
671
724
|
return details;
|
|
@@ -675,6 +728,8 @@ function summarizePayloadItems(payload) {
|
|
|
675
728
|
["results", "results"],
|
|
676
729
|
["tools", "tools"],
|
|
677
730
|
["servers", "servers"],
|
|
731
|
+
["running_servers", "servers"],
|
|
732
|
+
["sessions", "sessions"],
|
|
678
733
|
["skills", "skills"],
|
|
679
734
|
["prompts", "prompts"],
|
|
680
735
|
["libraries", "libraries"],
|
|
@@ -682,26 +737,12 @@ function summarizePayloadItems(payload) {
|
|
|
682
737
|
["rooms", "rooms"],
|
|
683
738
|
["messages", "messages"],
|
|
684
739
|
];
|
|
685
|
-
const getItemLabel = (value) => {
|
|
686
|
-
if (typeof value === "string" && value.trim())
|
|
687
|
-
return truncateText(value.trim(), 96);
|
|
688
|
-
if (!value || typeof value !== "object")
|
|
689
|
-
return null;
|
|
690
|
-
const item = value;
|
|
691
|
-
const label = stringValue(item.title) ??
|
|
692
|
-
stringValue(item.name) ??
|
|
693
|
-
stringValue(item.key) ??
|
|
694
|
-
stringValue(item.id) ??
|
|
695
|
-
stringValue(item.url) ??
|
|
696
|
-
stringValue(item.server_id);
|
|
697
|
-
return label ? truncateText(label, 96) : null;
|
|
698
|
-
};
|
|
699
740
|
if (Array.isArray(payload)) {
|
|
700
741
|
return {
|
|
701
742
|
count: payload.length,
|
|
702
743
|
label: payload.length === 1 ? "item" : "items",
|
|
703
744
|
items: payload
|
|
704
|
-
.map(
|
|
745
|
+
.map(summarizeInventoryItem)
|
|
705
746
|
.filter((item) => Boolean(item))
|
|
706
747
|
.slice(0, 4),
|
|
707
748
|
};
|
|
@@ -717,7 +758,7 @@ function summarizePayloadItems(payload) {
|
|
|
717
758
|
count: items.length,
|
|
718
759
|
label: items.length === 1 ? label.replace(/s$/, "") : label,
|
|
719
760
|
items: items
|
|
720
|
-
.map(
|
|
761
|
+
.map(summarizeInventoryItem)
|
|
721
762
|
.filter((item) => Boolean(item))
|
|
722
763
|
.slice(0, 4),
|
|
723
764
|
};
|
|
@@ -755,24 +796,55 @@ function summarizeRenderedResult(request, details, isError) {
|
|
|
755
796
|
return { status: "failed", preview };
|
|
756
797
|
}
|
|
757
798
|
const payload = extractJsonFromMcpResult(details) ?? details;
|
|
758
|
-
const listSummary = summarizePayloadItems(payload);
|
|
759
|
-
if (listSummary) {
|
|
760
|
-
return {
|
|
761
|
-
status: `${listSummary.count} ${listSummary.label}`,
|
|
762
|
-
preview: listSummary.items,
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
799
|
if (payload && typeof payload === "object") {
|
|
766
800
|
const obj = payload;
|
|
801
|
+
const explicitStatus = stringValue(obj.status);
|
|
802
|
+
if (request?.domain === "hub" && request.action === "start") {
|
|
803
|
+
const tools = Array.isArray(obj.tools)
|
|
804
|
+
? obj.tools
|
|
805
|
+
.map((item) => summarizeInventoryItem(item))
|
|
806
|
+
.filter((item) => Boolean(item))
|
|
807
|
+
.slice(0, 3)
|
|
808
|
+
: [];
|
|
809
|
+
const preview = [
|
|
810
|
+
...tools,
|
|
811
|
+
...[
|
|
812
|
+
stringValue(obj.hint),
|
|
813
|
+
stringValue(obj.message),
|
|
814
|
+
stringValue(obj.summary),
|
|
815
|
+
]
|
|
816
|
+
.filter((item) => Boolean(item))
|
|
817
|
+
.map((item) => truncateText(item, 120)),
|
|
818
|
+
].slice(0, 3);
|
|
819
|
+
return {
|
|
820
|
+
status: explicitStatus ?? defaultOperationStatus(request),
|
|
821
|
+
preview,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
const listSummary = summarizePayloadItems(payload);
|
|
825
|
+
if (listSummary) {
|
|
826
|
+
const preview = listSummary.items.length
|
|
827
|
+
? listSummary.items
|
|
828
|
+
: stringValue(obj.hint)
|
|
829
|
+
? [truncateText(stringValue(obj.hint) ?? "", 120)]
|
|
830
|
+
: [];
|
|
831
|
+
return {
|
|
832
|
+
status: `${listSummary.count} ${listSummary.label}`,
|
|
833
|
+
preview,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
767
836
|
const message = stringValue(obj.message) ??
|
|
768
837
|
stringValue(obj.summary) ??
|
|
769
|
-
stringValue(obj.
|
|
838
|
+
stringValue(obj.hint);
|
|
770
839
|
if (message) {
|
|
771
840
|
return {
|
|
772
|
-
status: defaultOperationStatus(request),
|
|
841
|
+
status: explicitStatus ?? defaultOperationStatus(request),
|
|
773
842
|
preview: [truncateText(message, 120)],
|
|
774
843
|
};
|
|
775
844
|
}
|
|
845
|
+
if (explicitStatus) {
|
|
846
|
+
return { status: explicitStatus, preview: [] };
|
|
847
|
+
}
|
|
776
848
|
if (obj.ok === true) {
|
|
777
849
|
return { status: defaultOperationStatus(request), preview: [] };
|
|
778
850
|
}
|
|
@@ -1461,6 +1533,7 @@ export class SageHookRuntime {
|
|
|
1461
1533
|
}
|
|
1462
1534
|
const SageDomainType = Type.Union([
|
|
1463
1535
|
Type.Literal("prompts"),
|
|
1536
|
+
Type.Literal("behaviors"),
|
|
1464
1537
|
Type.Literal("skills"),
|
|
1465
1538
|
Type.Literal("builder"),
|
|
1466
1539
|
Type.Literal("governance"),
|
|
@@ -1471,6 +1544,8 @@ const SageDomainType = Type.Union([
|
|
|
1471
1544
|
Type.Literal("security"),
|
|
1472
1545
|
Type.Literal("meta"),
|
|
1473
1546
|
Type.Literal("hub"),
|
|
1547
|
+
Type.Literal("activation"),
|
|
1548
|
+
Type.Literal("wallet"),
|
|
1474
1549
|
Type.Literal("library"),
|
|
1475
1550
|
Type.Literal("help"),
|
|
1476
1551
|
Type.Literal("external"),
|
|
@@ -1479,20 +1554,18 @@ export function createSageSearchTool(mcpBridge) {
|
|
|
1479
1554
|
return {
|
|
1480
1555
|
name: "sage_search",
|
|
1481
1556
|
label: "Sage: Search",
|
|
1482
|
-
promptSnippet: "
|
|
1557
|
+
promptSnippet: "Route read-only Sage code-mode requests: discover domains/actions, inspect context, query hub/external tools, and search capabilities",
|
|
1483
1558
|
promptGuidelines: [
|
|
1484
|
-
"Use sage_search (NOT bash sage commands) for
|
|
1485
|
-
"
|
|
1486
|
-
"
|
|
1487
|
-
"
|
|
1559
|
+
"Use sage_search (NOT bash sage commands) for Sage read/discovery flows such as help.list, prompts.search, behaviors.show, meta.get_project_context, meta.suggest, hub.list_servers/status, wallet.balance/address, and external.search",
|
|
1560
|
+
"Use sage_search({ domain: 'help', action: 'list', params: {} }) before guessing unknown Sage domains or actions",
|
|
1561
|
+
"Use sage_search({ domain: 'hub', action: 'list_servers', params: {} }) for MCP server inventory; use domain='external', action='list_servers' only to inspect running external servers and tool exposure",
|
|
1562
|
+
"If Sage says an action is a mutation or non-search inspect action, retry with sage_execute instead of falling back to bash",
|
|
1488
1563
|
],
|
|
1489
|
-
description: "
|
|
1490
|
-
"
|
|
1491
|
-
"
|
|
1492
|
-
"
|
|
1493
|
-
"
|
|
1494
|
-
"For library docs: domain='external', action='search', params={tool_name:'get_library_docs', tool_params:{context7CompatibleLibraryID:'/lib/id'}}. " +
|
|
1495
|
-
"To list available external servers/tools: domain='external', action='list_servers'.",
|
|
1564
|
+
description: "Route read-only Sage code-mode requests across prompts, behaviors, skills, builder, governance, chat, social, RLM, security, meta, hub, activation, wallet, library, help, and external services. " +
|
|
1565
|
+
"Start with domain='help', action='list' when you do not know the route. " +
|
|
1566
|
+
"Use domain='meta', action='get_project_context' for local Sage state and domain='meta', action='suggest' with params={goal:'...'} when you need route suggestions. " +
|
|
1567
|
+
"Use domain='hub' for MCP hub inventory/status and domain='external' for external tool discovery/search. " +
|
|
1568
|
+
"Some heavy inspect actions such as prompts.get and skills.get are classified by Sage as non-search and should use sage_execute.",
|
|
1496
1569
|
parameters: Type.Object({
|
|
1497
1570
|
domain: Type.Optional(SageDomainType),
|
|
1498
1571
|
action: Type.Optional(Type.String()),
|
|
@@ -1543,16 +1616,15 @@ export function createSageExecuteTool(mcpBridge) {
|
|
|
1543
1616
|
return {
|
|
1544
1617
|
name: "sage_execute",
|
|
1545
1618
|
label: "Sage: Execute",
|
|
1546
|
-
promptSnippet: "Execute mutations:
|
|
1619
|
+
promptSnippet: "Execute Sage mutations and non-search inspect actions: activate skills, fetch full prompt/skill bodies, manage hub servers, and call external tools",
|
|
1547
1620
|
promptGuidelines: [
|
|
1548
|
-
"Use sage_execute (NOT bash sage commands) for
|
|
1621
|
+
"Use sage_execute (NOT bash sage commands) for Sage mutations and for Sage actions that the code-mode router classifies as non-search, including prompts.get, skills.get/use, behaviors.run, activation.activate/deactivate, library writes, hub.start/stop, and external.call/execute",
|
|
1622
|
+
"After starting an MCP server with sage_execute({ domain: 'hub', action: 'start', params: { server_id: '...' } }), call its tools through domain='external' with params { tool_name, tool_params } instead of invoking sage via bash",
|
|
1549
1623
|
"To fetch a URL: sage_execute({ domain: 'external', action: 'call', params: { tool_name: 'fetch', tool_params: { url: 'https://...' } } })",
|
|
1550
1624
|
],
|
|
1551
|
-
description: "Execute mutations
|
|
1552
|
-
"
|
|
1553
|
-
"
|
|
1554
|
-
"To call an external tool: domain='external', action='call', params={tool_name:'tool_name', tool_params:{...}}. " +
|
|
1555
|
-
"To fetch a URL: domain='external', action='call', params={tool_name:'fetch', tool_params:{url:'https://example.com'}}.",
|
|
1625
|
+
description: "Execute Sage mutations and non-search inspect actions across prompts, behaviors, skills, builder, chat, library, activation, hub, and external tool surfaces. " +
|
|
1626
|
+
"Use this for prompts.get, skills.get/use, behaviors.run, activation.activate, library.prompt_add/skill_add, hub.start/stop, and external.call/execute. " +
|
|
1627
|
+
"Use domain='external', action='call' with params={tool_name:'fetch', tool_params:{url:'https://example.com'}} for URL fetches, and use domain='hub' to start or stop MCP servers before calling their tools.",
|
|
1556
1628
|
parameters: Type.Object({
|
|
1557
1629
|
domain: Type.Optional(SageDomainType),
|
|
1558
1630
|
action: Type.Optional(Type.String()),
|
|
@@ -1585,34 +1657,7 @@ export function createSageExecuteTool(mcpBridge) {
|
|
|
1585
1657
|
return toToolResultText(result);
|
|
1586
1658
|
}
|
|
1587
1659
|
catch (error) {
|
|
1588
|
-
|
|
1589
|
-
// Detect hub start failures due to missing credentials and add helpful context
|
|
1590
|
-
try {
|
|
1591
|
-
const normalized = normalizeCodeModeRequest("execute", params);
|
|
1592
|
-
if (normalized.domain === "hub" &&
|
|
1593
|
-
normalized.action === "start" &&
|
|
1594
|
-
/EOF|empty|parse|unexpected end/i.test(errorMsg)) {
|
|
1595
|
-
const serverId = String(normalized.params.server_id ?? "unknown");
|
|
1596
|
-
const knownKeyRequirements = {
|
|
1597
|
-
"brave-search": "BRAVE_API_KEY",
|
|
1598
|
-
github: "GITHUB_TOKEN",
|
|
1599
|
-
};
|
|
1600
|
-
const requiredKey = knownKeyRequirements[serverId];
|
|
1601
|
-
if (requiredKey) {
|
|
1602
|
-
errorMsg +=
|
|
1603
|
-
`. Server '${serverId}' likely failed because ${requiredKey} is not configured. ` +
|
|
1604
|
-
`Set it with: sage mcp hub env set ${serverId} ${requiredKey}=<your-key>`;
|
|
1605
|
-
}
|
|
1606
|
-
else {
|
|
1607
|
-
errorMsg +=
|
|
1608
|
-
`. Server '${serverId}' may require environment variables (API keys). ` +
|
|
1609
|
-
`Check with: sage mcp hub env list ${serverId}`;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
catch {
|
|
1614
|
-
// normalizeCodeModeRequest can throw -- ignore, keep original errorMsg
|
|
1615
|
-
}
|
|
1660
|
+
const errorMsg = augmentHubStartErrorMessage(params, error instanceof Error ? error.message : String(error));
|
|
1616
1661
|
return {
|
|
1617
1662
|
content: [
|
|
1618
1663
|
{
|
|
@@ -1627,21 +1672,269 @@ export function createSageExecuteTool(mcpBridge) {
|
|
|
1627
1672
|
};
|
|
1628
1673
|
}
|
|
1629
1674
|
const SAGE_CLI_REFERENCE = `
|
|
1630
|
-
## Sage
|
|
1675
|
+
## Sage Code-Mode Reference
|
|
1631
1676
|
|
|
1632
|
-
###
|
|
1633
|
-
- sage_search({ domain: "
|
|
1634
|
-
- sage_search({ domain: "
|
|
1677
|
+
### Route Discovery
|
|
1678
|
+
- List domains/actions: sage_search({ domain: "help", action: "list", params: {} })
|
|
1679
|
+
- Suggest the right route: sage_search({ domain: "meta", action: "suggest", params: { goal: "..." } })
|
|
1680
|
+
- Inspect local Sage state: sage_search({ domain: "meta", action: "get_project_context", params: {} })
|
|
1635
1681
|
|
|
1636
|
-
###
|
|
1637
|
-
-
|
|
1638
|
-
-
|
|
1639
|
-
-
|
|
1682
|
+
### Skills / Prompts / Behaviors
|
|
1683
|
+
- Search skills: sage_search({ domain: "skills", action: "search", params: { query: "..." } })
|
|
1684
|
+
- Inspect skill body: sage_execute({ domain: "skills", action: "get", params: { key: "..." } })
|
|
1685
|
+
- Activate skill: sage_execute({ domain: "skills", action: "use", params: { key: "..." } })
|
|
1686
|
+
- Search prompts: sage_search({ domain: "prompts", action: "search", params: { query: "..." } })
|
|
1687
|
+
- Inspect prompt body: sage_execute({ domain: "prompts", action: "get", params: { key: "..." } })
|
|
1688
|
+
- Inspect behavior: sage_search({ domain: "behaviors", action: "show", params: { key: "..." } })
|
|
1640
1689
|
|
|
1641
|
-
### Hub
|
|
1642
|
-
-
|
|
1643
|
-
-
|
|
1690
|
+
### Hub / External Tools
|
|
1691
|
+
- List MCP servers: sage_search({ domain: "hub", action: "list_servers", params: {} })
|
|
1692
|
+
- Hub status: sage_search({ domain: "hub", action: "status", params: {} })
|
|
1693
|
+
- Start server: sage_execute({ domain: "hub", action: "start", params: { server_id: "github" } })
|
|
1694
|
+
- Running external servers/tools: sage_search({ domain: "external", action: "list_servers", params: {} })
|
|
1695
|
+
- Web search: sage_search({ domain: "external", action: "search", params: { tool_name: "web_search", tool_params: { query: "..." } } })
|
|
1696
|
+
- URL fetch: sage_execute({ domain: "external", action: "call", params: { tool_name: "fetch", tool_params: { url: "https://..." } } })
|
|
1644
1697
|
`.trim();
|
|
1698
|
+
function buildHubRenderContext(action, params, context) {
|
|
1699
|
+
return {
|
|
1700
|
+
...(context && typeof context === "object" ? context : {}),
|
|
1701
|
+
args: {
|
|
1702
|
+
domain: "hub",
|
|
1703
|
+
action,
|
|
1704
|
+
params,
|
|
1705
|
+
},
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
function augmentHubStartErrorMessage(params, errorMsg) {
|
|
1709
|
+
try {
|
|
1710
|
+
const normalized = normalizeCodeModeRequest("execute", params);
|
|
1711
|
+
if (normalized.domain === "hub" &&
|
|
1712
|
+
normalized.action === "start" &&
|
|
1713
|
+
/EOF|empty|parse|unexpected end/i.test(errorMsg)) {
|
|
1714
|
+
const serverId = String(normalized.params.server_id ?? "unknown");
|
|
1715
|
+
const knownKeyRequirements = {
|
|
1716
|
+
"brave-search": "BRAVE_API_KEY",
|
|
1717
|
+
github: "GITHUB_TOKEN",
|
|
1718
|
+
};
|
|
1719
|
+
const requiredKey = knownKeyRequirements[serverId];
|
|
1720
|
+
if (requiredKey) {
|
|
1721
|
+
return (errorMsg +
|
|
1722
|
+
`. Server '${serverId}' likely failed because ${requiredKey} is not configured. ` +
|
|
1723
|
+
`Set it with: sage mcp hub env set ${serverId} ${requiredKey}=<your-key>`);
|
|
1724
|
+
}
|
|
1725
|
+
return (errorMsg +
|
|
1726
|
+
`. Server '${serverId}' may require environment variables (API keys). ` +
|
|
1727
|
+
`Check with: sage mcp hub env list ${serverId}`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
catch {
|
|
1731
|
+
// Keep original error text if normalization fails.
|
|
1732
|
+
}
|
|
1733
|
+
return errorMsg;
|
|
1734
|
+
}
|
|
1735
|
+
export function createHubListServersTool(mcpBridge) {
|
|
1736
|
+
return {
|
|
1737
|
+
name: "hub_list_servers",
|
|
1738
|
+
label: "Hub: List Servers",
|
|
1739
|
+
promptSnippet: "List Sage MCP servers directly when you need inventory/status without composing a generic sage_search call",
|
|
1740
|
+
promptGuidelines: [
|
|
1741
|
+
"Prefer hub_list_servers() for simple MCP server inventory in Pi; it is the direct convenience wrapper over domain='hub', action='list_servers'",
|
|
1742
|
+
"Use sage_search({ domain: 'external', action: 'list_servers', params: {} }) only when you specifically need running external servers/tools rather than the full Sage hub inventory",
|
|
1743
|
+
],
|
|
1744
|
+
description: "List all Sage MCP servers with running/available state. This is a Pi-native convenience wrapper for sage_search({ domain:'hub', action:'list_servers', params:{} }).",
|
|
1745
|
+
parameters: Type.Object({}),
|
|
1746
|
+
renderCall: (_args, theme) => renderSageToolCall("search", { domain: "hub", action: "list_servers", params: {} }, theme),
|
|
1747
|
+
renderResult: (result, options, theme, context) => renderSageToolResult("search", result, options, theme, buildHubRenderContext("list_servers", {}, context)),
|
|
1748
|
+
execute: async () => {
|
|
1749
|
+
try {
|
|
1750
|
+
const result = await callCodeModeWithWebResilience(mcpBridge, "search", {
|
|
1751
|
+
domain: "hub",
|
|
1752
|
+
action: "list_servers",
|
|
1753
|
+
params: {},
|
|
1754
|
+
});
|
|
1755
|
+
return toToolResultText(result);
|
|
1756
|
+
}
|
|
1757
|
+
catch (error) {
|
|
1758
|
+
return {
|
|
1759
|
+
content: [
|
|
1760
|
+
{
|
|
1761
|
+
type: "text",
|
|
1762
|
+
text: `hub_list_servers failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1763
|
+
},
|
|
1764
|
+
],
|
|
1765
|
+
isError: true,
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
export function createHubStatusTool(mcpBridge) {
|
|
1772
|
+
return {
|
|
1773
|
+
name: "hub_status",
|
|
1774
|
+
label: "Hub: Status",
|
|
1775
|
+
promptSnippet: "Inspect Sage MCP hub runtime status directly when you want running server state without composing a generic sage_search call",
|
|
1776
|
+
promptGuidelines: [
|
|
1777
|
+
"Prefer hub_status() for simple MCP runtime status in Pi; it is the direct convenience wrapper over domain='hub', action='status'",
|
|
1778
|
+
"Use sage_status() when you need full adapter health plus Sage context, not just hub runtime state",
|
|
1779
|
+
],
|
|
1780
|
+
description: "Inspect Sage MCP hub runtime status. This is a Pi-native convenience wrapper for sage_search({ domain:'hub', action:'status', params:{} }).",
|
|
1781
|
+
parameters: Type.Object({}),
|
|
1782
|
+
renderCall: (_args, theme) => renderSageToolCall("search", { domain: "hub", action: "status", params: {} }, theme),
|
|
1783
|
+
renderResult: (result, options, theme, context) => renderSageToolResult("search", result, options, theme, buildHubRenderContext("status", {}, context)),
|
|
1784
|
+
execute: async () => {
|
|
1785
|
+
try {
|
|
1786
|
+
const result = await callCodeModeWithWebResilience(mcpBridge, "search", {
|
|
1787
|
+
domain: "hub",
|
|
1788
|
+
action: "status",
|
|
1789
|
+
params: {},
|
|
1790
|
+
});
|
|
1791
|
+
return toToolResultText(result);
|
|
1792
|
+
}
|
|
1793
|
+
catch (error) {
|
|
1794
|
+
return {
|
|
1795
|
+
content: [
|
|
1796
|
+
{
|
|
1797
|
+
type: "text",
|
|
1798
|
+
text: `hub_status failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1799
|
+
},
|
|
1800
|
+
],
|
|
1801
|
+
isError: true,
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
export function createHubSessionListTool(mcpBridge) {
|
|
1808
|
+
return {
|
|
1809
|
+
name: "hub_session_list",
|
|
1810
|
+
label: "Hub: Session List",
|
|
1811
|
+
promptSnippet: "List active Sage MCP hub sessions directly when you need isolated instance visibility without composing a generic sage_search call",
|
|
1812
|
+
promptGuidelines: [
|
|
1813
|
+
"Prefer hub_session_list() for simple hub session inspection in Pi; it wraps domain='hub', action='session_list'",
|
|
1814
|
+
"Pass server_id when you want to filter sessions for one server, and use hub_stop_server with server_id plus session_id to stop a specific isolated session",
|
|
1815
|
+
],
|
|
1816
|
+
description: "List active Sage MCP hub sessions, optionally filtered by server_id. This is a Pi-native convenience wrapper for sage_search({ domain:'hub', action:'session_list', params:{ server_id? } }).",
|
|
1817
|
+
parameters: Type.Object({
|
|
1818
|
+
server_id: Type.Optional(Type.String({
|
|
1819
|
+
description: "Optional hub server identifier to filter sessions",
|
|
1820
|
+
})),
|
|
1821
|
+
}),
|
|
1822
|
+
renderCall: (args, theme) => renderSageToolCall("search", {
|
|
1823
|
+
domain: "hub",
|
|
1824
|
+
action: "session_list",
|
|
1825
|
+
params: normalizeParams(args),
|
|
1826
|
+
}, theme),
|
|
1827
|
+
renderResult: (result, options, theme, context) => renderSageToolResult("search", result, options, theme, buildHubRenderContext("session_list", normalizeParams(context?.args), context)),
|
|
1828
|
+
execute: async (_toolCallId, params) => {
|
|
1829
|
+
try {
|
|
1830
|
+
const payload = normalizeParams(params);
|
|
1831
|
+
const result = await callCodeModeWithWebResilience(mcpBridge, "search", {
|
|
1832
|
+
domain: "hub",
|
|
1833
|
+
action: "session_list",
|
|
1834
|
+
params: payload,
|
|
1835
|
+
});
|
|
1836
|
+
return toToolResultText(result);
|
|
1837
|
+
}
|
|
1838
|
+
catch (error) {
|
|
1839
|
+
return {
|
|
1840
|
+
content: [
|
|
1841
|
+
{
|
|
1842
|
+
type: "text",
|
|
1843
|
+
text: `hub_session_list failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1844
|
+
},
|
|
1845
|
+
],
|
|
1846
|
+
isError: true,
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
},
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
export function createHubStopServerTool(mcpBridge) {
|
|
1853
|
+
return {
|
|
1854
|
+
name: "hub_stop_server",
|
|
1855
|
+
label: "Hub: Stop Server",
|
|
1856
|
+
promptSnippet: "Stop a Sage MCP server directly when you know the server_id and want to end its runtime cleanly",
|
|
1857
|
+
promptGuidelines: [
|
|
1858
|
+
"Prefer hub_stop_server({ server_id: '...' }) for simple MCP server shutdown in Pi; it wraps domain='hub', action='stop'",
|
|
1859
|
+
"Use hub_status() or hub_list_servers() after stopping a server if you need to confirm the resulting hub state",
|
|
1860
|
+
],
|
|
1861
|
+
description: "Stop a Sage MCP server by server_id. This is a Pi-native convenience wrapper for sage_execute({ domain:'hub', action:'stop', params:{ server_id:'...' } }).",
|
|
1862
|
+
parameters: Type.Object({
|
|
1863
|
+
server_id: Type.String({
|
|
1864
|
+
description: "Hub server identifier to stop, such as github or brave-search",
|
|
1865
|
+
}),
|
|
1866
|
+
session_id: Type.Optional(Type.String({
|
|
1867
|
+
description: "Optional hub session identifier for isolated instances",
|
|
1868
|
+
})),
|
|
1869
|
+
}),
|
|
1870
|
+
renderCall: (args, theme) => renderSageToolCall("execute", { domain: "hub", action: "stop", params: normalizeParams(args) }, theme),
|
|
1871
|
+
renderResult: (result, options, theme, context) => renderSageToolResult("execute", result, options, theme, buildHubRenderContext("stop", normalizeParams(context?.args), context)),
|
|
1872
|
+
execute: async (_toolCallId, params) => {
|
|
1873
|
+
try {
|
|
1874
|
+
const payload = normalizeParams(params);
|
|
1875
|
+
const result = await callCodeModeWithWebResilience(mcpBridge, "execute", {
|
|
1876
|
+
domain: "hub",
|
|
1877
|
+
action: "stop",
|
|
1878
|
+
params: payload,
|
|
1879
|
+
});
|
|
1880
|
+
return toToolResultText(result);
|
|
1881
|
+
}
|
|
1882
|
+
catch (error) {
|
|
1883
|
+
return {
|
|
1884
|
+
content: [
|
|
1885
|
+
{
|
|
1886
|
+
type: "text",
|
|
1887
|
+
text: `hub_stop_server failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1888
|
+
},
|
|
1889
|
+
],
|
|
1890
|
+
isError: true,
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
},
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
export function createHubStartServerTool(mcpBridge) {
|
|
1897
|
+
return {
|
|
1898
|
+
name: "hub_start_server",
|
|
1899
|
+
label: "Hub: Start Server",
|
|
1900
|
+
promptSnippet: "Start a Sage MCP server directly when you already know the server_id and want Pi to expose its tools",
|
|
1901
|
+
promptGuidelines: [
|
|
1902
|
+
"Prefer hub_start_server({ server_id: '...' }) for simple MCP server startup in Pi; it wraps domain='hub', action='start'",
|
|
1903
|
+
"After starting an external MCP server, call its tools through sage_execute/sage_search with domain='external' and params { tool_name, tool_params }",
|
|
1904
|
+
],
|
|
1905
|
+
description: "Start a Sage MCP server by server_id. This is a Pi-native convenience wrapper for sage_execute({ domain:'hub', action:'start', params:{ server_id:'...' } }).",
|
|
1906
|
+
parameters: Type.Object({
|
|
1907
|
+
server_id: Type.String({
|
|
1908
|
+
description: "Hub server identifier to start, such as github or brave-search",
|
|
1909
|
+
}),
|
|
1910
|
+
session_id: Type.Optional(Type.String({
|
|
1911
|
+
description: "Optional hub session identifier for isolated instances",
|
|
1912
|
+
})),
|
|
1913
|
+
}),
|
|
1914
|
+
renderCall: (args, theme) => renderSageToolCall("execute", { domain: "hub", action: "start", params: normalizeParams(args) }, theme),
|
|
1915
|
+
renderResult: (result, options, theme, context) => renderSageToolResult("execute", result, options, theme, buildHubRenderContext("start", normalizeParams(context?.args), context)),
|
|
1916
|
+
execute: async (_toolCallId, params) => {
|
|
1917
|
+
try {
|
|
1918
|
+
const payload = normalizeParams(params);
|
|
1919
|
+
const result = await callCodeModeWithWebResilience(mcpBridge, "execute", {
|
|
1920
|
+
domain: "hub",
|
|
1921
|
+
action: "start",
|
|
1922
|
+
params: payload,
|
|
1923
|
+
});
|
|
1924
|
+
return toToolResultText(result);
|
|
1925
|
+
}
|
|
1926
|
+
catch (error) {
|
|
1927
|
+
const errorMsg = augmentHubStartErrorMessage({ domain: "hub", action: "start", params }, error instanceof Error ? error.message : String(error));
|
|
1928
|
+
return {
|
|
1929
|
+
content: [
|
|
1930
|
+
{ type: "text", text: `hub_start_server failed: ${errorMsg}` },
|
|
1931
|
+
],
|
|
1932
|
+
isError: true,
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1645
1938
|
export function createSageStatusTool(mcpBridge) {
|
|
1646
1939
|
return {
|
|
1647
1940
|
name: "sage_status",
|
|
@@ -1791,6 +2084,11 @@ export function createSageExtensions(options) {
|
|
|
1791
2084
|
console.error("[sage-pi-adapter] failed to start MCP bridge:", error);
|
|
1792
2085
|
});
|
|
1793
2086
|
const sageTools = [
|
|
2087
|
+
createHubListServersTool(mcpBridge),
|
|
2088
|
+
createHubStatusTool(mcpBridge),
|
|
2089
|
+
createHubSessionListTool(mcpBridge),
|
|
2090
|
+
createHubStartServerTool(mcpBridge),
|
|
2091
|
+
createHubStopServerTool(mcpBridge),
|
|
1794
2092
|
createSageSearchTool(mcpBridge),
|
|
1795
2093
|
createSageExecuteTool(mcpBridge),
|
|
1796
2094
|
createSageStatusTool(mcpBridge),
|