chainlesschain 0.162.37 → 0.162.38
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/package.json +3 -2
- package/src/assets/web-panel/assets/{AIOps-_oxz4VHy.js → AIOps-DV0Q9zKL.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-uaeqFuDj.js → ActionButton-C6vH8rhL.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BPVV0OUf.js → Analytics-BvPDc2ui.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-ppCYKm3I.js → AppLayout-CWnyqTqY.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-DFAY6umk.js → Audit-BzenidV4.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-pAPBFDyP.js → Backup-CSl7bNwK.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BbBl0uT2.js → BaseInput-DAY3iHIq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Ct22JUnT.js → Chat-Jyhm9fgk.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DPlsLl22.js → ChatBubbleRenderer-CwlAnVjy.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DEkCollc.js → Checkbox-D4rwURAi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Tor-de39.js → Codegen-DYdjTEfC.js} +1 -1
- package/src/assets/web-panel/assets/{Col-ojNrLQU7.js → Col-DsVyZ_fS.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOGhqMF.js → Community-CjCpl27Q.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CYKNlSZ4.js → Compact-kt18dsjm.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-C5E6ABuA.js → Compliance-BV5urquU.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CHeEsZ3W.js → Cowork-C4SovPWC.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B4e1n2e7.js → Cron-uuNs_xzA.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DbNV8P9R.js → Crosschain-DR5a65tR.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C5_Tk3nC.js → DID-B1KTf2-5.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BhdV_c4N.js → Dashboard-Dkj7XgED.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CEi5AMtM.js → Dropdown-BhXCuJ19.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DOhPiYng.js → EmailListRenderer-DG8365Iv.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-fu4NRP3X.js → FamilyGuardDashboard-BdHGPu39.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B7BtIWKL.js → Federation-Dwvxl0zR.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BmPWZVLP.js → FormItemContext-BVmhCVWU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-hsOPNJq8.js → GenericCardRenderer-DDPjvF2s.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bi_EFBUH.js → Git-foK6WTSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-emf2ubDK.js → Governance-CfqMdu6Y.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B7KjKzkI.js → Inference-BKrLO4GO.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-uAaBK0F3.js → KnowledgeGraph-6o6Q-mmF.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-utK7hNpj.js → Logs-L5ZIW0Dz.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CzQe6n3z.js → Marketplace-BWkfEocP.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CuAaJr51.js → McpTools-BPebQbWU.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CRuZZJ75.js → Memory-C0Dq-X3C.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Cp06wunh.js → MobileBridge-DRBoutTY.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-DJEdUwhr.js → MobileProjects-BMP6eLp1.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-8YY4dR7g.js → Mtc-Cj3QPM9p.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BmPJYHar.js → MtcAudit-rBQYbfQR.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-d-ydyVdq.js → Multisig-Dbuy4OY4.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DA_ikw_n.js → NLProgramming-CMnt1se-.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DIyF-fRe.js → Notes-BX9tSCiF.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CzPZXEtK.js → NotificationSettings-BFeirVRq.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ybiMlKQW.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DdDZ_Ap6.js → Organization-kTfRxKqk.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BnMBkttv.js → Overflow-CtuCAzwV.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Es1050f-.js → P2P-KfbciaP3.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CKkRmyn9.js → PdhVaultBrowser-bqEUFhgC.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-zU9n9cAD.js → Permissions-BgMypz-z.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BZi5Xwas.js → PersonalDataHub-C3zUE-1z.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CRfeGiFc.js → Pipeline-iX-pYHpC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CQA_IgLA.js → Privacy-B01uzeFM.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C9hmEvoT.js → ProjectInit-TsfbzJp7.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-yXA72ws4.js → ProjectSettings-iGvMp8sM.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BpWS-qam.js → Projects-Be9k29iQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cxe55dRD.js → Providers-C9Pc8dqo.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Do0aUTQr.js → QuickAsk-DN_yFiVO.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend--ysZHjyA.js → Recommend-CvSNgl7H.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BOBU8JrH.js → Reputation-S6BCz8xH.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C6X7bRKE.js → Row-CTRYCaqP.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D8AwqlkQ.js → RssFeed-Cu8_P5ll.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Bi3rCZD4.js → Search-rZ1Xza_U.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DxUDVrtY.js → Security-CF43IJHX.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BXXN7yC1.js → Services-BobNHzne.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B3BR34tZ.js → Skeleton-DWJ2kfuI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BjYu8OQ1.js → Skills-AmEZgHYr.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDkCtD8w.js → Sla-DTS-fBiY.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CGhYzP7V.js → SpeechSettings-DEr6MHRU.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYNKVAHA.js → SyncSettings-CVs9alv_.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DavmlJpd.js → Tasks-BcVDAxdi.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CQuYFf2C.js → Templates-CTNjZRKA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DdzZh8vE.js → Tenant-DPbXg0Pg.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DhKXcPw2.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-DKOARnc_.js → TimelineRenderer-B0DMZOpk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D7QRNG8y.js → Tokens-RvWuBXgg.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCsqLZl4.js → Trigger-2O-BaTQG.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BarGUa6p.js → Trust-6qY35L-C.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-pHrg5a8E.js → UkeySign-DhV1wYtQ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Dug3m1py.js → VideoEditing-DgqA5UZm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BfK3Z_Ez.js → Wallet-DJRYdUAK.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CYRdl9td.js → WebAuthn-C2W-x0cg.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DTW5AcqM.js → WorkflowEditor-BP2tkDHe.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CCXz4j38.js → chat-CGVfeoTn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BJBOhAqa.js → colors-BmjRolM1.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-E9M6BQcM.js → compact-item-BvJJkjZE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cg9CAws4.js → createContext-DyhlvRYs.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CetO0WH0.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DhVtqv5L.js → hasIn-BoBMR89s.js} +1 -1
- package/src/assets/web-panel/assets/{index--7o5YdL6.js → index-39VDXdn6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSiyjCYi.js → index-81tWFqfN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJ8nNT8h.js → index-BT1SQ9nj.js} +1 -1
- package/src/assets/web-panel/assets/index-BZVz-WfV.js +1 -0
- package/src/assets/web-panel/assets/{index-ComyTKz-.js → index-Beh7jDbS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwvewrul.js → index-Bm_MmdwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5yC2Ps8.js → index-BqGNmoKy.js} +1 -1
- package/src/assets/web-panel/assets/{index-_PNqQ5mE.js → index-BuQrONgf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8bjEHrQ.js → index-BvvNnWXe.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVR_s-pT.js → index-ByWpNjTj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFp-wdrQ.js → index-BycpeGfj.js} +1 -1
- package/src/assets/web-panel/assets/{index-wkt-o5q5.js → index-C0xn6hOr.js} +1 -1
- package/src/assets/web-panel/assets/{index-B111fZ21.js → index-C1t-r7yV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_SMPD4L.js → index-CDPMHKQi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAB0nGP7.js → index-CIaGw7vl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFarAlXj.js → index-CQJVedQ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSQazU6J.js → index-CSgbOGaP.js} +1 -1
- package/src/assets/web-panel/assets/{index-MdXEhfdJ.js → index-Cbh-lCxq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaFe1aqY.js → index-CzDVBBcg.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByazO4Q9.js → index-Czsbrn75.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm1m7BJh.js → index-D-93XwJd.js} +1 -1
- package/src/assets/web-panel/assets/index-D0-bvFy3.js +1 -0
- package/src/assets/web-panel/assets/{index-BxSzyly9.js → index-D0YToIi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeRlLp3F.js → index-DIPZ6hbJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkTeBHI9.js → index-DeeLHcMY.js} +1 -1
- package/src/assets/web-panel/assets/{index-kz1oXl1a.js → index-DgbWSwr5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca8BYV1g.js → index-DtKdCXHW.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChsSljaN.js → index-DwTgvhOL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdhnGez0.js → index-DyS4I4L-.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUp_c8Le.js → index-FKFT-QTk.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-2dUIli.js → index-Te0ruvY_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDcJO27F.js → index-VXVukhBA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CznfPnOx.js → index-Y1b8i0NV.js} +3 -3
- package/src/assets/web-panel/assets/{index-BFZPRd0T.js → index-ZNIms1nA.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4NBF4Sa.js → index-n-N19np-.js} +1 -1
- package/src/assets/web-panel/assets/{index-4N5lNXGP.js → index-vF1pR00A.js} +1 -1
- package/src/assets/web-panel/assets/{index-6-04M2Nx.js → index-wLAjVpmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7DXdf7x.js → index-xPSzUoWT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di5LBXcE.js → index-xZdOioVg.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-iyBaePF-.js → initDefaultProps-BLKSE8he.js} +1 -1
- package/src/assets/web-panel/assets/{motion-RWtj4rgu.js → motion-Bb59qqLK.js} +1 -1
- package/src/assets/web-panel/assets/{move-CqPRVzpH.js → move-CB3pYCk6.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DsvJze25.js → omit-iImQWuU7.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4tfZBhc.js → pickAttrs-DRP2Chqo.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-KvHUwXMA.js → placementArrow-BrlfD4tF.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DGdJ-b7W.js → responsiveObserve-Cqxkuh5H.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cd6ebRmw.js → slide-nxKEuLMj.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bg9GcIAn.js → statusUtils-30E47KSk.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-MQjKsG84.js → styleChecker-Dn2_-5bn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C241WujP.js → useFlexGapSupport-DkZ00X6F.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CMpy7RS4.js → useFs-ByrwSCOr.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BLHtapKb.js → usePersonalDataHub-BDY6jtUD.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DmcTV67c.js → vnode-BL2q5BLv.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DHL8_0Y8.js → zoom-BSkPKE42.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +31 -0
- package/src/commands/mcp.js +236 -6
- package/src/harness/mcp-client.js +70 -1
- package/src/lib/settings-hooks.cjs +1 -0
- package/src/repl/agent-repl.js +52 -20
- package/src/repl/mcp-prompt.js +122 -0
- package/src/runtime/agent-core.js +123 -17
- package/src/runtime/headless-runner.js +34 -9
- package/src/runtime/mcp-config.js +118 -9
- package/src/assets/web-panel/assets/OrderTableRenderer-BiLtg-LY.js +0 -1
- package/src/assets/web-panel/assets/Terminal-D75WeG9d.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BrsbTJUv.js +0 -1
- package/src/assets/web-panel/assets/index-DSTQDO-Y.js +0 -1
- package/src/assets/web-panel/assets/index-c2U6LV3Q.js +0 -1
package/src/commands/mcp.js
CHANGED
|
@@ -38,6 +38,49 @@ function getClient() {
|
|
|
38
38
|
return mcpClient;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Connect MCP server(s) for a one-shot query (resources / prompts). Connects
|
|
43
|
+
* the named server, or every registered server when `serverName` is omitted.
|
|
44
|
+
* Returns `{ client, connected }`; the caller must `await shutdown()`.
|
|
45
|
+
*/
|
|
46
|
+
async function connectForQuery(program, serverName) {
|
|
47
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
48
|
+
if (!ctx.db) {
|
|
49
|
+
logger.error("Database not available");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const db = ctx.db.getDatabase();
|
|
53
|
+
const config = new MCPServerConfig(db);
|
|
54
|
+
let rows;
|
|
55
|
+
if (serverName) {
|
|
56
|
+
const row = config.get(serverName);
|
|
57
|
+
if (!row) {
|
|
58
|
+
logger.error(
|
|
59
|
+
`Server "${serverName}" not configured. Use 'mcp add' first.`,
|
|
60
|
+
);
|
|
61
|
+
await shutdown();
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
rows = [row];
|
|
65
|
+
} else {
|
|
66
|
+
rows = config.list();
|
|
67
|
+
}
|
|
68
|
+
const client = new MCPClient();
|
|
69
|
+
const connected = [];
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
if (!row) continue;
|
|
72
|
+
try {
|
|
73
|
+
await client.connect(row.name, row);
|
|
74
|
+
connected.push(row.name);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.log(
|
|
77
|
+
chalk.yellow(` Failed to connect "${row.name}": ${err.message}`),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { client, connected };
|
|
82
|
+
}
|
|
83
|
+
|
|
41
84
|
// Phase 3 (Hosted MCP Policy): resolve runtime mode from --mode > env > local.
|
|
42
85
|
function resolveMode(options) {
|
|
43
86
|
return (
|
|
@@ -68,10 +111,20 @@ export function registerMcpCommand(program) {
|
|
|
68
111
|
// mcp login — OAuth 2.0 (Auth Code + PKCE) for a remote MCP server.
|
|
69
112
|
mcp
|
|
70
113
|
.command("login <url>")
|
|
71
|
-
.description(
|
|
114
|
+
.description(
|
|
115
|
+
"Authorize a remote MCP server via OAuth (opens a browser); stores the token",
|
|
116
|
+
)
|
|
72
117
|
.option("--scope <scope>", "OAuth scope(s) to request")
|
|
73
|
-
.option(
|
|
74
|
-
|
|
118
|
+
.option(
|
|
119
|
+
"--client-id <id>",
|
|
120
|
+
"Use a pre-registered client_id instead of dynamic registration",
|
|
121
|
+
)
|
|
122
|
+
.option(
|
|
123
|
+
"--port <n>",
|
|
124
|
+
"Localhost callback port",
|
|
125
|
+
(v) => parseInt(v, 10),
|
|
126
|
+
53682,
|
|
127
|
+
)
|
|
75
128
|
.option("--no-open", "Print the authorize URL instead of opening a browser")
|
|
76
129
|
.action(async (url, options) => {
|
|
77
130
|
try {
|
|
@@ -109,7 +162,8 @@ export function registerMcpCommand(program) {
|
|
|
109
162
|
.description("Delete the stored OAuth token for a remote MCP server")
|
|
110
163
|
.action(async (url) => {
|
|
111
164
|
try {
|
|
112
|
-
const { deleteStoredToken, serverKey } =
|
|
165
|
+
const { deleteStoredToken, serverKey } =
|
|
166
|
+
await import("../lib/mcp-oauth.js");
|
|
113
167
|
const ok = deleteStoredToken(url);
|
|
114
168
|
logger.log(
|
|
115
169
|
ok
|
|
@@ -129,7 +183,8 @@ export function registerMcpCommand(program) {
|
|
|
129
183
|
.option("--json", "Output as JSON")
|
|
130
184
|
.action(async (options) => {
|
|
131
185
|
try {
|
|
132
|
-
const { loadTokenStore, isTokenExpired } =
|
|
186
|
+
const { loadTokenStore, isTokenExpired } =
|
|
187
|
+
await import("../lib/mcp-oauth.js");
|
|
133
188
|
const store = loadTokenStore();
|
|
134
189
|
const rows = Object.values(store).map((r) => ({
|
|
135
190
|
server: r.server,
|
|
@@ -142,7 +197,9 @@ export function registerMcpCommand(program) {
|
|
|
142
197
|
return;
|
|
143
198
|
}
|
|
144
199
|
if (rows.length === 0) {
|
|
145
|
-
logger.log(
|
|
200
|
+
logger.log(
|
|
201
|
+
chalk.gray("No MCP OAuth tokens. Run: cc mcp login <url>"),
|
|
202
|
+
);
|
|
146
203
|
return;
|
|
147
204
|
}
|
|
148
205
|
for (const r of rows) {
|
|
@@ -566,6 +623,179 @@ export function registerMcpCommand(program) {
|
|
|
566
623
|
}
|
|
567
624
|
});
|
|
568
625
|
|
|
626
|
+
// mcp resources — list resources exposed by configured servers
|
|
627
|
+
mcp
|
|
628
|
+
.command("resources")
|
|
629
|
+
.description("List resources exposed by MCP servers")
|
|
630
|
+
.option("-s, --server <name>", "Filter by / connect only this server")
|
|
631
|
+
.option("--json", "Output as JSON")
|
|
632
|
+
.action(async (options) => {
|
|
633
|
+
try {
|
|
634
|
+
const { client } = await connectForQuery(program, options.server);
|
|
635
|
+
const resources = client.listResources(options.server);
|
|
636
|
+
if (options.json) {
|
|
637
|
+
console.log(JSON.stringify(resources, null, 2));
|
|
638
|
+
} else if (resources.length === 0) {
|
|
639
|
+
logger.info("No resources available.");
|
|
640
|
+
} else {
|
|
641
|
+
logger.log(chalk.bold(`MCP Resources (${resources.length}):\n`));
|
|
642
|
+
for (const r of resources) {
|
|
643
|
+
logger.log(` ${chalk.cyan(r.uri)} ${chalk.gray(`[${r.server}]`)}`);
|
|
644
|
+
if (r.name) logger.log(` ${chalk.gray(r.name)}`);
|
|
645
|
+
if (r.description) logger.log(` ${chalk.gray(r.description)}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
await client.disconnectAll();
|
|
649
|
+
await shutdown();
|
|
650
|
+
} catch (err) {
|
|
651
|
+
logger.error(`Failed: ${err.message}`);
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// mcp read-resource — read a resource's contents by URI
|
|
657
|
+
mcp
|
|
658
|
+
.command("read-resource")
|
|
659
|
+
.description("Read an MCP resource by URI")
|
|
660
|
+
.argument("<uri>", "Resource URI")
|
|
661
|
+
.option("-s, --server <name>", "Server that owns the resource")
|
|
662
|
+
.option("--json", "Output as JSON")
|
|
663
|
+
.action(async (uri, options) => {
|
|
664
|
+
try {
|
|
665
|
+
const { client } = await connectForQuery(program, options.server);
|
|
666
|
+
let server = options.server;
|
|
667
|
+
if (!server) {
|
|
668
|
+
const match = client.listResources().find((r) => r.uri === uri);
|
|
669
|
+
if (!match) {
|
|
670
|
+
logger.error(
|
|
671
|
+
`Resource "${uri}" not found. Run 'mcp resources' to list URIs.`,
|
|
672
|
+
);
|
|
673
|
+
await client.disconnectAll();
|
|
674
|
+
await shutdown();
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
server = match.server;
|
|
678
|
+
}
|
|
679
|
+
const result = await client.readResource(server, uri);
|
|
680
|
+
if (options.json) {
|
|
681
|
+
console.log(JSON.stringify(result, null, 2));
|
|
682
|
+
} else if (Array.isArray(result?.contents)) {
|
|
683
|
+
for (const c of result.contents) {
|
|
684
|
+
if (typeof c.text === "string") {
|
|
685
|
+
logger.log(c.text);
|
|
686
|
+
} else if (c.blob) {
|
|
687
|
+
logger.log(
|
|
688
|
+
chalk.gray(
|
|
689
|
+
`[Binary: ${c.mimeType || "application/octet-stream"}]`,
|
|
690
|
+
),
|
|
691
|
+
);
|
|
692
|
+
} else {
|
|
693
|
+
logger.log(JSON.stringify(c, null, 2));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
logger.log(JSON.stringify(result, null, 2));
|
|
698
|
+
}
|
|
699
|
+
await client.disconnectAll();
|
|
700
|
+
await shutdown();
|
|
701
|
+
} catch (err) {
|
|
702
|
+
logger.error(`Read failed: ${err.message}`);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// mcp prompts — list prompts (server-provided slash commands)
|
|
708
|
+
mcp
|
|
709
|
+
.command("prompts")
|
|
710
|
+
.description("List prompts exposed by MCP servers")
|
|
711
|
+
.option("-s, --server <name>", "Filter by / connect only this server")
|
|
712
|
+
.option("--json", "Output as JSON")
|
|
713
|
+
.action(async (options) => {
|
|
714
|
+
try {
|
|
715
|
+
const { client } = await connectForQuery(program, options.server);
|
|
716
|
+
const prompts = client.listPrompts(options.server);
|
|
717
|
+
if (options.json) {
|
|
718
|
+
console.log(JSON.stringify(prompts, null, 2));
|
|
719
|
+
} else if (prompts.length === 0) {
|
|
720
|
+
logger.info("No prompts available.");
|
|
721
|
+
} else {
|
|
722
|
+
logger.log(chalk.bold(`MCP Prompts (${prompts.length}):\n`));
|
|
723
|
+
for (const p of prompts) {
|
|
724
|
+
logger.log(
|
|
725
|
+
` ${chalk.cyan(`/mcp__${p.server}__${p.name}`)} ${chalk.gray(`[${p.server}]`)}`,
|
|
726
|
+
);
|
|
727
|
+
if (p.description) logger.log(` ${chalk.gray(p.description)}`);
|
|
728
|
+
if (Array.isArray(p.arguments) && p.arguments.length > 0) {
|
|
729
|
+
const argNames = p.arguments
|
|
730
|
+
.map((a) => (a.required ? `${a.name}*` : a.name))
|
|
731
|
+
.join(", ");
|
|
732
|
+
logger.log(` ${chalk.gray(`args: ${argNames}`)}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
await client.disconnectAll();
|
|
737
|
+
await shutdown();
|
|
738
|
+
} catch (err) {
|
|
739
|
+
logger.error(`Failed: ${err.message}`);
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// mcp get-prompt — fetch a rendered prompt by name
|
|
745
|
+
mcp
|
|
746
|
+
.command("get-prompt")
|
|
747
|
+
.description("Fetch a rendered MCP prompt by name")
|
|
748
|
+
.argument("<name>", "Prompt name")
|
|
749
|
+
.option("-s, --server <name>", "Server that owns the prompt")
|
|
750
|
+
.option("-a, --args <json>", "Prompt arguments as JSON")
|
|
751
|
+
.option("--json", "Output as JSON")
|
|
752
|
+
.action(async (name, options) => {
|
|
753
|
+
try {
|
|
754
|
+
const { client } = await connectForQuery(program, options.server);
|
|
755
|
+
let server = options.server;
|
|
756
|
+
if (!server) {
|
|
757
|
+
const match = client.listPrompts().find((p) => p.name === name);
|
|
758
|
+
if (!match) {
|
|
759
|
+
logger.error(
|
|
760
|
+
`Prompt "${name}" not found. Run 'mcp prompts' to list prompts.`,
|
|
761
|
+
);
|
|
762
|
+
await client.disconnectAll();
|
|
763
|
+
await shutdown();
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
server = match.server;
|
|
767
|
+
}
|
|
768
|
+
const args = options.args ? JSON.parse(options.args) : {};
|
|
769
|
+
const result = await client.getPrompt(server, name, args);
|
|
770
|
+
if (options.json) {
|
|
771
|
+
console.log(JSON.stringify(result, null, 2));
|
|
772
|
+
} else {
|
|
773
|
+
if (result?.description) {
|
|
774
|
+
logger.log(chalk.gray(result.description) + "\n");
|
|
775
|
+
}
|
|
776
|
+
for (const msg of result?.messages || []) {
|
|
777
|
+
const blocks = Array.isArray(msg.content)
|
|
778
|
+
? msg.content
|
|
779
|
+
: [msg.content];
|
|
780
|
+
for (const b of blocks) {
|
|
781
|
+
if (b && b.type === "text") {
|
|
782
|
+
logger.log(`${chalk.gray(`[${msg.role}]`)} ${b.text}`);
|
|
783
|
+
} else {
|
|
784
|
+
logger.log(
|
|
785
|
+
`${chalk.gray(`[${msg.role}]`)} ${JSON.stringify(b)}`,
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
await client.disconnectAll();
|
|
792
|
+
await shutdown();
|
|
793
|
+
} catch (err) {
|
|
794
|
+
logger.error(`Get prompt failed: ${err.message}`);
|
|
795
|
+
process.exit(1);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
569
799
|
// mcp scaffold — generate a boilerplate MCP server project
|
|
570
800
|
mcp
|
|
571
801
|
.command("scaffold <name>")
|
|
@@ -156,7 +156,7 @@ export class MCPClient extends EventEmitter {
|
|
|
156
156
|
// Initialize MCP protocol
|
|
157
157
|
const initResult = await this._sendRequest(name, "initialize", {
|
|
158
158
|
protocolVersion: "2024-11-05",
|
|
159
|
-
capabilities: { tools: {}, resources: {} },
|
|
159
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
160
160
|
clientInfo: { name: "chainlesschain-cli", version: "0.37.9" },
|
|
161
161
|
});
|
|
162
162
|
|
|
@@ -187,12 +187,21 @@ export class MCPClient extends EventEmitter {
|
|
|
187
187
|
// Server may not support resources
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// Fetch available prompts (server-provided slash commands)
|
|
191
|
+
try {
|
|
192
|
+
const promptsResult = await this._sendRequest(name, "prompts/list", {});
|
|
193
|
+
entry.prompts = promptsResult?.prompts || [];
|
|
194
|
+
} catch {
|
|
195
|
+
// Server may not support prompts
|
|
196
|
+
}
|
|
197
|
+
|
|
190
198
|
this.emit("server-connected", { name, tools: entry.tools.length });
|
|
191
199
|
return {
|
|
192
200
|
name,
|
|
193
201
|
state: entry.state,
|
|
194
202
|
tools: entry.tools,
|
|
195
203
|
resources: entry.resources,
|
|
204
|
+
prompts: entry.prompts,
|
|
196
205
|
serverInfo: entry.serverInfo,
|
|
197
206
|
};
|
|
198
207
|
} catch (err) {
|
|
@@ -250,6 +259,7 @@ export class MCPClient extends EventEmitter {
|
|
|
250
259
|
state: entry.state,
|
|
251
260
|
tools: entry.tools.length,
|
|
252
261
|
resources: entry.resources.length,
|
|
262
|
+
prompts: (entry.prompts || []).length,
|
|
253
263
|
serverInfo: entry.serverInfo || {},
|
|
254
264
|
});
|
|
255
265
|
}
|
|
@@ -296,6 +306,26 @@ export class MCPClient extends EventEmitter {
|
|
|
296
306
|
return result;
|
|
297
307
|
}
|
|
298
308
|
|
|
309
|
+
/**
|
|
310
|
+
* List resources from a specific server or all servers. Each resource is
|
|
311
|
+
* annotated with its owning `server` (mirrors `listTools`).
|
|
312
|
+
*/
|
|
313
|
+
listResources(serverName) {
|
|
314
|
+
if (serverName) {
|
|
315
|
+
const entry = this.servers.get(serverName);
|
|
316
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
317
|
+
return (entry.resources || []).map((r) => ({ ...r, server: serverName }));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const all = [];
|
|
321
|
+
for (const [name, entry] of this.servers) {
|
|
322
|
+
for (const r of entry.resources || []) {
|
|
323
|
+
all.push({ ...r, server: name });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return all;
|
|
327
|
+
}
|
|
328
|
+
|
|
299
329
|
/**
|
|
300
330
|
* Read a resource from a server.
|
|
301
331
|
*/
|
|
@@ -309,6 +339,45 @@ export class MCPClient extends EventEmitter {
|
|
|
309
339
|
return result;
|
|
310
340
|
}
|
|
311
341
|
|
|
342
|
+
/**
|
|
343
|
+
* List prompts from a specific server or all servers. Each prompt is
|
|
344
|
+
* annotated with its owning `server` (mirrors `listTools`).
|
|
345
|
+
*/
|
|
346
|
+
listPrompts(serverName) {
|
|
347
|
+
if (serverName) {
|
|
348
|
+
const entry = this.servers.get(serverName);
|
|
349
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
350
|
+
return (entry.prompts || []).map((p) => ({ ...p, server: serverName }));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const all = [];
|
|
354
|
+
for (const [name, entry] of this.servers) {
|
|
355
|
+
for (const p of entry.prompts || []) {
|
|
356
|
+
all.push({ ...p, server: name });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return all;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Fetch a rendered prompt (`prompts/get`) from a server. `args` is a map of
|
|
364
|
+
* the prompt's named arguments to string values. Returns the server's result
|
|
365
|
+
* `{ description?, messages: [...] }`.
|
|
366
|
+
*/
|
|
367
|
+
async getPrompt(serverName, promptName, args = {}) {
|
|
368
|
+
const entry = this.servers.get(serverName);
|
|
369
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
370
|
+
if (entry.state !== ServerState.CONNECTED) {
|
|
371
|
+
throw new Error(`Server "${serverName}" is not connected`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const result = await this._sendRequest(serverName, "prompts/get", {
|
|
375
|
+
name: promptName,
|
|
376
|
+
arguments: args || {},
|
|
377
|
+
});
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
312
381
|
// ─── Internal JSON-RPC transport ──────────────────────────────
|
|
313
382
|
|
|
314
383
|
_sendRequest(serverName, method, params) {
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -74,6 +74,7 @@ import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
|
74
74
|
import { composeSystemPrompt } from "../runtime/system-prompt.js";
|
|
75
75
|
import { makeFallbackChatFn } from "../runtime/fallback-model.js";
|
|
76
76
|
import { resolveSlashMacro } from "./slash-macro.js";
|
|
77
|
+
import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* Reference to the runtime DB for hook execution (set during startAgentRepl)
|
|
@@ -129,7 +130,8 @@ async function _persistAlwaysAllow(tool, args) {
|
|
|
129
130
|
scope: "local",
|
|
130
131
|
});
|
|
131
132
|
if (!_permissionRules) _permissionRules = { allow: [], ask: [], deny: [] };
|
|
132
|
-
if (!_permissionRules.allow.includes(rule))
|
|
133
|
+
if (!_permissionRules.allow.includes(rule))
|
|
134
|
+
_permissionRules.allow.push(rule);
|
|
133
135
|
return { rule, file };
|
|
134
136
|
} catch (err) {
|
|
135
137
|
process.stderr.write(` always-allow persist failed: ${err.message}\n`);
|
|
@@ -504,9 +506,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
504
506
|
// settings.json SessionStart hooks → inject session context (observe-only).
|
|
505
507
|
if (_settingsHooks) {
|
|
506
508
|
try {
|
|
507
|
-
const { runSessionStartHooks } =
|
|
508
|
-
"../lib/settings-hook-events.cjs"
|
|
509
|
-
);
|
|
509
|
+
const { runSessionStartHooks } =
|
|
510
|
+
await import("../lib/settings-hook-events.cjs");
|
|
510
511
|
const ctx = runSessionStartHooks(_settingsHooks, {
|
|
511
512
|
source: "startup",
|
|
512
513
|
cwd: process.cwd(),
|
|
@@ -805,7 +806,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
805
806
|
if (_statusLineEnabled && _renderStatus) {
|
|
806
807
|
const line = _renderStatus();
|
|
807
808
|
// Built-in line is dimmed; a custom command may carry its own ANSI.
|
|
808
|
-
if (line)
|
|
809
|
+
if (line)
|
|
810
|
+
process.stdout.write((_customStatus ? line : chalk.dim(line)) + "\n");
|
|
809
811
|
}
|
|
810
812
|
rl.setPrompt(getPrompt());
|
|
811
813
|
rl.prompt();
|
|
@@ -1024,15 +1026,16 @@ export async function startAgentRepl(options = {}) {
|
|
|
1024
1026
|
logger.info("Status line: on");
|
|
1025
1027
|
} else {
|
|
1026
1028
|
// bare / "show" → report state + a one-off render
|
|
1027
|
-
const line =
|
|
1029
|
+
const line =
|
|
1030
|
+
_statusLineEnabled && _renderStatus ? _renderStatus() : null;
|
|
1028
1031
|
if (line) {
|
|
1029
|
-
logger.info(
|
|
1030
|
-
`Status line: ${_customStatus ? line : chalk.dim(line)}`,
|
|
1031
|
-
);
|
|
1032
|
+
logger.info(`Status line: ${_customStatus ? line : chalk.dim(line)}`);
|
|
1032
1033
|
} else {
|
|
1033
1034
|
logger.info(
|
|
1034
1035
|
`Status line: ${_statusLineEnabled ? "on (no content yet)" : "off"}` +
|
|
1035
|
-
(_statusLineEnabled
|
|
1036
|
+
(_statusLineEnabled
|
|
1037
|
+
? ""
|
|
1038
|
+
: ` — enable with ${chalk.cyan("/statusline on")}`),
|
|
1036
1039
|
);
|
|
1037
1040
|
}
|
|
1038
1041
|
if (_customStatus) {
|
|
@@ -1046,9 +1049,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
1046
1049
|
if (trimmed === "/output-style" || trimmed.startsWith("/output-style ")) {
|
|
1047
1050
|
const arg = trimmed.slice("/output-style".length).trim();
|
|
1048
1051
|
try {
|
|
1049
|
-
const { discoverOutputStyles, getOutputStyle } =
|
|
1050
|
-
"../lib/output-styles.js"
|
|
1051
|
-
);
|
|
1052
|
+
const { discoverOutputStyles, getOutputStyle } =
|
|
1053
|
+
await import("../lib/output-styles.js");
|
|
1052
1054
|
if (!arg) {
|
|
1053
1055
|
logger.log(chalk.bold("Output styles:"));
|
|
1054
1056
|
for (const s of discoverOutputStyles(process.cwd())) {
|
|
@@ -1801,15 +1803,47 @@ export async function startAgentRepl(options = {}) {
|
|
|
1801
1803
|
return;
|
|
1802
1804
|
}
|
|
1803
1805
|
|
|
1806
|
+
// `/mcp` — overview of connected MCP servers' resources + prompts.
|
|
1807
|
+
if (trimmed === "/mcp" || trimmed === "/mcp ") {
|
|
1808
|
+
const mcpClient = _adhocMcp?.mcpClient || _bundleMcpClient;
|
|
1809
|
+
logger.log(renderMcpSurface(mcpClient));
|
|
1810
|
+
prompt();
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1804
1814
|
// User-defined slash-command macros (.claude/commands/*.md), Claude-Code
|
|
1805
1815
|
// parity. resolveSlashMacro maps a leading /name to a command macro and
|
|
1806
1816
|
// expands its template; a non-match returns the line unchanged so a literal
|
|
1807
1817
|
// prompt like "/etc/hosts" still reaches the LLM. Wire is unit-tested.
|
|
1808
1818
|
let promptText = trimmed;
|
|
1819
|
+
|
|
1820
|
+
// MCP server-provided prompts (Claude-Code parity): `/mcp__<server>__<name>
|
|
1821
|
+
// [json-args]` fetches a rendered prompt template from the connected MCP
|
|
1822
|
+
// server and uses its text as this turn's input. Falls through unchanged
|
|
1823
|
+
// when the line isn't an MCP prompt command.
|
|
1824
|
+
if (promptText.startsWith("/mcp__")) {
|
|
1825
|
+
try {
|
|
1826
|
+
const expanded = await expandMcpPrompt(
|
|
1827
|
+
promptText,
|
|
1828
|
+
_adhocMcp?.mcpClient || _bundleMcpClient,
|
|
1829
|
+
);
|
|
1830
|
+
if (expanded != null) {
|
|
1831
|
+
promptText = expanded;
|
|
1832
|
+
logger.log(chalk.gray(`[mcp] prompt expanded`));
|
|
1833
|
+
}
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
logger.info(
|
|
1836
|
+
chalk.yellow(`[mcp] prompt expansion failed: ${err.message}`),
|
|
1837
|
+
);
|
|
1838
|
+
prompt();
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1809
1842
|
try {
|
|
1810
1843
|
const macro = await resolveSlashMacro(trimmed, { cwd: process.cwd() });
|
|
1811
1844
|
if (macro.matched) {
|
|
1812
|
-
for (const w of macro.warnings)
|
|
1845
|
+
for (const w of macro.warnings)
|
|
1846
|
+
logger.info(chalk.yellow(`[@ref] ${w}`));
|
|
1813
1847
|
promptText = macro.promptText;
|
|
1814
1848
|
logger.log(
|
|
1815
1849
|
chalk.gray(`[/${macro.name}] macro expanded (${macro.scope})`),
|
|
@@ -1864,9 +1898,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
1864
1898
|
// is observe-only). block → abort the turn; context → inject before the turn.
|
|
1865
1899
|
if (_settingsHooks) {
|
|
1866
1900
|
try {
|
|
1867
|
-
const { runUserPromptSubmitHooks } =
|
|
1868
|
-
"../lib/settings-hook-events.cjs"
|
|
1869
|
-
);
|
|
1901
|
+
const { runUserPromptSubmitHooks } =
|
|
1902
|
+
await import("../lib/settings-hook-events.cjs");
|
|
1870
1903
|
const ups = runUserPromptSubmitHooks(_settingsHooks, {
|
|
1871
1904
|
prompt: userContent,
|
|
1872
1905
|
cwd: process.cwd(),
|
|
@@ -2136,9 +2169,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
2136
2169
|
// settings.json SessionEnd hooks (observe-only) when the REPL exits.
|
|
2137
2170
|
if (_settingsHooks) {
|
|
2138
2171
|
try {
|
|
2139
|
-
const { runObserveHooks } =
|
|
2140
|
-
"../lib/settings-hook-events.cjs"
|
|
2141
|
-
);
|
|
2172
|
+
const { runObserveHooks } =
|
|
2173
|
+
await import("../lib/settings-hook-events.cjs");
|
|
2142
2174
|
runObserveHooks(
|
|
2143
2175
|
_settingsHooks,
|
|
2144
2176
|
"SessionEnd",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP prompt + resource surfacing for the agent REPL (Claude-Code parity).
|
|
3
|
+
*
|
|
4
|
+
* MCP servers can expose "prompts" — server-defined, parameterized prompt
|
|
5
|
+
* templates — which Claude Code surfaces as slash commands of the form
|
|
6
|
+
* `/mcp__<server>__<prompt>`. This module is the pure, unit-testable core:
|
|
7
|
+
* it parses such a command, fetches the rendered prompt from the connected
|
|
8
|
+
* MCP client, and flattens it into plain text to use as the user's turn.
|
|
9
|
+
*
|
|
10
|
+
* It also renders a `/mcp` overview of every connected server's resources and
|
|
11
|
+
* prompts. The REPL (agent-repl.js) wires these in; all the logic lives here so
|
|
12
|
+
* it can be tested without spinning up a readline session.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse a `/mcp__<server>__<prompt> [args]` line. Returns `{ server, name,
|
|
17
|
+
* args }` or `null` when the line is not an MCP prompt invocation.
|
|
18
|
+
*
|
|
19
|
+
* `args` is parsed from a trailing JSON object when present; a non-JSON tail is
|
|
20
|
+
* passed as `{ input: "<tail>" }` so simple one-arg prompts stay ergonomic.
|
|
21
|
+
*/
|
|
22
|
+
export function parseMcpPromptCommand(line) {
|
|
23
|
+
const trimmed = (line || "").trim();
|
|
24
|
+
if (!trimmed.startsWith("/mcp__")) return null;
|
|
25
|
+
|
|
26
|
+
const sp = trimmed.search(/\s/);
|
|
27
|
+
const token = sp === -1 ? trimmed : trimmed.slice(0, sp);
|
|
28
|
+
const rest = sp === -1 ? "" : trimmed.slice(sp + 1).trim();
|
|
29
|
+
|
|
30
|
+
const full = token.slice(1); // drop leading "/"
|
|
31
|
+
const parts = full.split("__"); // ["mcp", "<server>", "<prompt...>"]
|
|
32
|
+
if (parts.length < 3 || parts[0] !== "mcp") return null;
|
|
33
|
+
const server = parts[1];
|
|
34
|
+
const name = parts.slice(2).join("__");
|
|
35
|
+
if (!server || !name) return null;
|
|
36
|
+
|
|
37
|
+
let args = {};
|
|
38
|
+
if (rest) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(rest);
|
|
41
|
+
args =
|
|
42
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
43
|
+
? parsed
|
|
44
|
+
: { input: rest };
|
|
45
|
+
} catch {
|
|
46
|
+
args = { input: rest };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { server, name, args };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Flatten an MCP `prompts/get` result (`{ messages: [...] }`) into plain text.
|
|
54
|
+
* Handles text blocks and embedded text resources; ignores binary blocks.
|
|
55
|
+
*/
|
|
56
|
+
export function renderPromptMessages(result) {
|
|
57
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const msg of messages) {
|
|
60
|
+
const blocks = Array.isArray(msg?.content) ? msg.content : [msg?.content];
|
|
61
|
+
for (const b of blocks) {
|
|
62
|
+
if (!b) continue;
|
|
63
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
64
|
+
out.push(b.text);
|
|
65
|
+
} else if (
|
|
66
|
+
b.type === "resource" &&
|
|
67
|
+
b.resource &&
|
|
68
|
+
typeof b.resource.text === "string"
|
|
69
|
+
) {
|
|
70
|
+
out.push(b.resource.text);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out.join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Expand a `/mcp__server__prompt` line into prompt text by calling the MCP
|
|
79
|
+
* client. Returns the rendered text, or `null` when the line is not an MCP
|
|
80
|
+
* prompt command. Throws if the underlying `getPrompt` fails (caller decides
|
|
81
|
+
* whether to surface or swallow).
|
|
82
|
+
*/
|
|
83
|
+
export async function expandMcpPrompt(line, mcpClient) {
|
|
84
|
+
const cmd = parseMcpPromptCommand(line);
|
|
85
|
+
if (!cmd) return null;
|
|
86
|
+
if (!mcpClient || typeof mcpClient.getPrompt !== "function") {
|
|
87
|
+
throw new Error("No MCP servers are connected this session.");
|
|
88
|
+
}
|
|
89
|
+
const result = await mcpClient.getPrompt(cmd.server, cmd.name, cmd.args);
|
|
90
|
+
return renderPromptMessages(result);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Render a human overview of all connected MCP resources + prompts for `/mcp`.
|
|
95
|
+
*/
|
|
96
|
+
export function renderMcpSurface(mcpClient) {
|
|
97
|
+
if (!mcpClient) return "No MCP servers are connected this session.";
|
|
98
|
+
const resources =
|
|
99
|
+
typeof mcpClient.listResources === "function"
|
|
100
|
+
? mcpClient.listResources()
|
|
101
|
+
: [];
|
|
102
|
+
const prompts =
|
|
103
|
+
typeof mcpClient.listPrompts === "function" ? mcpClient.listPrompts() : [];
|
|
104
|
+
|
|
105
|
+
const lines = [];
|
|
106
|
+
lines.push(`MCP resources (${resources.length}):`);
|
|
107
|
+
for (const r of resources) {
|
|
108
|
+
lines.push(` ${r.uri} [${r.server}]${r.name ? " — " + r.name : ""}`);
|
|
109
|
+
}
|
|
110
|
+
lines.push("");
|
|
111
|
+
lines.push(`MCP prompts (${prompts.length}):`);
|
|
112
|
+
for (const p of prompts) {
|
|
113
|
+
lines.push(
|
|
114
|
+
` /mcp__${p.server}__${p.name}${p.description ? " — " + p.description : ""}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (resources.length === 0 && prompts.length === 0) {
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push("(connected servers expose no resources or prompts)");
|
|
120
|
+
}
|
|
121
|
+
return lines.join("\n");
|
|
122
|
+
}
|