@muxoai/platform 1.4.1 → 1.4.2
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +1 -1
- package/dist/cli.js +6 -3
- package/dist/composio-catalog.d.ts +177 -1
- package/dist/composio-catalog.js +1427 -7
- package/dist/config.d.ts +6 -0
- package/dist/config.js +11 -1
- package/dist/engine.d.ts +18 -1
- package/dist/engine.js +259 -35
- package/dist/mcp-schema.d.ts +162 -1
- package/dist/mcp-schema.js +1523 -40
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +412 -23
- package/dist/policy.d.ts +108 -1
- package/dist/policy.js +406 -1
- package/dist/providers/composio-instant.js +38 -3
- package/dist/providers/composio-sdk.d.ts +59 -2
- package/dist/providers/composio-sdk.js +273 -59
- package/dist/providers/stripe-projects.d.ts +4 -0
- package/dist/providers/stripe-projects.js +548 -126
- package/dist/server.js +531 -61
- package/dist/store.js +2 -0
- package/dist/types.d.ts +15 -0
- package/dist/ui/console-html.d.ts +31 -1
- package/dist/ui/console-html.js +922 -368
- package/dist/ui/design-tokens.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +15 -8
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/hooks/session-start.sh +15 -3
- package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
- package/plugins/muxo/mcp.json +0 -10
- package/plugins/muxo/plugin.json +4 -4
- package/plugins/muxo/skills/muxo/SKILL.md +25 -16
- package/plugins/muxo/skills/muxo/references/cli.md +5 -1
- package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +1 -1
- package/src/catalogues/stripe-projects-catalog.json +140 -0
package/dist/mcp-schema.js
CHANGED
|
@@ -1,3 +1,318 @@
|
|
|
1
|
+
import { isStripeProjectsExclusiveProvider, resolveStripeProjectsCanonicalToolSlug, } from "./composio-catalog.js";
|
|
2
|
+
export const LATEST_MCP_PROTOCOL_VERSION = "2026-07-28";
|
|
3
|
+
export const DEFAULT_LEGACY_MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
4
|
+
/**
|
|
5
|
+
* Dual-era MCP protocol versions supported by Muxo:
|
|
6
|
+
* - Modern stateless era: 2026-07-28 (SEP-2575 server/discover, SEP-2549 caching, MRTR resultType)
|
|
7
|
+
* - Legacy session era: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05
|
|
8
|
+
*/
|
|
9
|
+
export const SUPPORTED_MCP_PROTOCOL_VERSIONS = [
|
|
10
|
+
"2026-07-28",
|
|
11
|
+
"2025-11-25",
|
|
12
|
+
"2025-06-18",
|
|
13
|
+
"2025-03-26",
|
|
14
|
+
"2024-11-05",
|
|
15
|
+
];
|
|
16
|
+
export const MCP_ERR_HEADER_MISMATCH = -32020;
|
|
17
|
+
export const MCP_ERR_MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
|
|
18
|
+
export const MCP_ERR_UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
19
|
+
export function isSupportedMcpProtocolVersion(version) {
|
|
20
|
+
if (typeof version !== "string" || !version.trim())
|
|
21
|
+
return false;
|
|
22
|
+
return SUPPORTED_MCP_PROTOCOL_VERSIONS.includes(version.trim());
|
|
23
|
+
}
|
|
24
|
+
function decodeBase64Utf8(b64) {
|
|
25
|
+
const normalized = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
26
|
+
if (typeof Buffer !== "undefined") {
|
|
27
|
+
return Buffer.from(normalized, "base64").toString("utf8");
|
|
28
|
+
}
|
|
29
|
+
const binary = atob(normalized);
|
|
30
|
+
const bytes = new Uint8Array(binary.length);
|
|
31
|
+
for (let i = 0; i < binary.length; i++) {
|
|
32
|
+
bytes[i] = binary.charCodeAt(i);
|
|
33
|
+
}
|
|
34
|
+
return new TextDecoder().decode(bytes);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decodes an MCP Streamable HTTP header value (`Mcp-Name`, `Mcp-Param-*`),
|
|
38
|
+
* supporting plain ASCII as well as the 2026-07-28 Base64 sentinel format (`=:base64:...:`)
|
|
39
|
+
* and RFC 2047 `=?utf-8?B?...?=`.
|
|
40
|
+
*/
|
|
41
|
+
export function decodeMcpHeaderValue(raw) {
|
|
42
|
+
const trimmed = String(raw || "").trim();
|
|
43
|
+
const sentinelMatch = /^=:base64:([A-Za-z0-9+/=_-]+):=$/.exec(trimmed) ||
|
|
44
|
+
/^=:base64:([A-Za-z0-9+/=_-]+):$/.exec(trimmed);
|
|
45
|
+
if (sentinelMatch) {
|
|
46
|
+
try {
|
|
47
|
+
return decodeBase64Utf8(sentinelMatch[1]);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return trimmed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const rfc2047Match = /^=\?utf-8\?B\?([A-Za-z0-9+/=]+)\?=$/i.exec(trimmed);
|
|
54
|
+
if (rfc2047Match) {
|
|
55
|
+
try {
|
|
56
|
+
return decodeBase64Utf8(rfc2047Match[1]);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return trimmed;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return trimmed;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validates MCP 2026-07-28 Streamable HTTP headers (`MCP-Protocol-Version`, `Mcp-Method`,
|
|
66
|
+
* `Mcp-Name`) and per-request `params._meta["io.modelcontextprotocol/protocolVersion"]`
|
|
67
|
+
* against the JSON-RPC request body.
|
|
68
|
+
*
|
|
69
|
+
* Returns `-32020 HeaderMismatch` when `Mcp-Method` or `Mcp-Name` conflicts with the body,
|
|
70
|
+
* and `-32022 UnsupportedProtocolVersion` when an unsupported protocol version is requested.
|
|
71
|
+
*/
|
|
72
|
+
export function validateMcpHttpHeadersAndMeta(headers, rpc) {
|
|
73
|
+
const message = rpc && typeof rpc === "object" && !Array.isArray(rpc)
|
|
74
|
+
? rpc
|
|
75
|
+
: {};
|
|
76
|
+
const id = message.id ?? null;
|
|
77
|
+
const bodyMethod = typeof message.method === "string" ? message.method : "";
|
|
78
|
+
const params = message.params && typeof message.params === "object" && !Array.isArray(message.params)
|
|
79
|
+
? message.params
|
|
80
|
+
: {};
|
|
81
|
+
const rawMeta = params._meta && typeof params._meta === "object" && !Array.isArray(params._meta)
|
|
82
|
+
? params._meta
|
|
83
|
+
: {};
|
|
84
|
+
// 1. Validate MCP-Protocol-Version HTTP header if provided
|
|
85
|
+
const headerVersion = typeof headers.protocolVersion === "string" ? headers.protocolVersion.trim() : "";
|
|
86
|
+
if (headerVersion && !isSupportedMcpProtocolVersion(headerVersion)) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
id,
|
|
90
|
+
error: {
|
|
91
|
+
code: MCP_ERR_UNSUPPORTED_PROTOCOL_VERSION,
|
|
92
|
+
message: `Unsupported MCP protocol version '${headerVersion}'. Supported versions: ${SUPPORTED_MCP_PROTOCOL_VERSIONS.join(", ")}.`,
|
|
93
|
+
data: {
|
|
94
|
+
requestedVersion: headerVersion,
|
|
95
|
+
supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// 2. Validate per-request _meta["io.modelcontextprotocol/protocolVersion"] if provided
|
|
101
|
+
const metaVersion = typeof rawMeta["io.modelcontextprotocol/protocolVersion"] === "string"
|
|
102
|
+
? rawMeta["io.modelcontextprotocol/protocolVersion"].trim()
|
|
103
|
+
: "";
|
|
104
|
+
if (metaVersion && !isSupportedMcpProtocolVersion(metaVersion)) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
id,
|
|
108
|
+
error: {
|
|
109
|
+
code: MCP_ERR_UNSUPPORTED_PROTOCOL_VERSION,
|
|
110
|
+
message: `Unsupported MCP protocol version '${metaVersion}' in params._meta. Supported versions: ${SUPPORTED_MCP_PROTOCOL_VERSIONS.join(", ")}.`,
|
|
111
|
+
data: {
|
|
112
|
+
requestedVersion: metaVersion,
|
|
113
|
+
supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// If both header and _meta specify a version, they must agree
|
|
119
|
+
if (headerVersion && metaVersion && headerVersion !== metaVersion) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
id,
|
|
123
|
+
error: {
|
|
124
|
+
code: MCP_ERR_HEADER_MISMATCH,
|
|
125
|
+
message: `HeaderMismatch: MCP-Protocol-Version header ('${headerVersion}') does not match params._meta["io.modelcontextprotocol/protocolVersion"] ('${metaVersion}').`,
|
|
126
|
+
data: {
|
|
127
|
+
header: "MCP-Protocol-Version",
|
|
128
|
+
headerValue: headerVersion,
|
|
129
|
+
bodyValue: metaVersion,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// 3. Validate Mcp-Method header against JSON-RPC method
|
|
135
|
+
const headerMethod = typeof headers.method === "string" ? headers.method.trim() : "";
|
|
136
|
+
if (headerMethod && bodyMethod && headerMethod !== bodyMethod) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
id,
|
|
140
|
+
error: {
|
|
141
|
+
code: MCP_ERR_HEADER_MISMATCH,
|
|
142
|
+
message: `HeaderMismatch: Mcp-Method header ('${headerMethod}') does not match JSON-RPC method ('${bodyMethod}').`,
|
|
143
|
+
data: {
|
|
144
|
+
header: "Mcp-Method",
|
|
145
|
+
headerValue: headerMethod,
|
|
146
|
+
bodyValue: bodyMethod,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// 4. Validate Mcp-Name header against params.name / params.uri
|
|
152
|
+
const rawHeaderName = typeof headers.name === "string" ? headers.name.trim() : "";
|
|
153
|
+
if (rawHeaderName) {
|
|
154
|
+
const decodedHeaderName = decodeMcpHeaderValue(rawHeaderName);
|
|
155
|
+
const bodyTargetName = bodyMethod === "resources/read"
|
|
156
|
+
? String(params.uri ?? "")
|
|
157
|
+
: String(params.name ?? params.uri ?? "");
|
|
158
|
+
if (bodyTargetName && decodedHeaderName !== bodyTargetName) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
id,
|
|
162
|
+
error: {
|
|
163
|
+
code: MCP_ERR_HEADER_MISMATCH,
|
|
164
|
+
message: `HeaderMismatch: Mcp-Name header ('${decodedHeaderName}') does not match request target ('${bodyTargetName}').`,
|
|
165
|
+
data: {
|
|
166
|
+
header: "Mcp-Name",
|
|
167
|
+
headerValue: decodedHeaderName,
|
|
168
|
+
bodyValue: bodyTargetName,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// 5. Validate initialize params.protocolVersion if explicitly provided
|
|
175
|
+
const initVersion = bodyMethod === "initialize" && typeof params.protocolVersion === "string"
|
|
176
|
+
? params.protocolVersion.trim()
|
|
177
|
+
: "";
|
|
178
|
+
if (initVersion && !isSupportedMcpProtocolVersion(initVersion)) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
id,
|
|
182
|
+
error: {
|
|
183
|
+
code: MCP_ERR_UNSUPPORTED_PROTOCOL_VERSION,
|
|
184
|
+
message: `Unsupported MCP protocol version '${initVersion}'. Supported versions: ${SUPPORTED_MCP_PROTOCOL_VERSIONS.join(", ")}.`,
|
|
185
|
+
data: {
|
|
186
|
+
requestedVersion: initVersion,
|
|
187
|
+
supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const resolvedVersion = metaVersion ||
|
|
193
|
+
initVersion ||
|
|
194
|
+
headerVersion ||
|
|
195
|
+
(bodyMethod === "server/discover"
|
|
196
|
+
? LATEST_MCP_PROTOCOL_VERSION
|
|
197
|
+
: DEFAULT_LEGACY_MCP_PROTOCOL_VERSION);
|
|
198
|
+
return { ok: true, protocolVersion: resolvedVersion };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Builds the canonical MCP 2026-07-28 `server/discover` response (SEP-2575 + SEP-2549).
|
|
202
|
+
* Includes both top-level `serverInfo`/`instructions` and `_meta["io.modelcontextprotocol/*"]`
|
|
203
|
+
* so both strict 2026-07-28 validators and dual-era clients succeed.
|
|
204
|
+
*/
|
|
205
|
+
export function buildMcpDiscoverResult(serverVersion) {
|
|
206
|
+
const serverInfo = {
|
|
207
|
+
name: "muxo-mcp-server",
|
|
208
|
+
title: "Muxo Unified Agent Runtime & Governance MCP Server",
|
|
209
|
+
version: serverVersion,
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
resultType: "complete",
|
|
213
|
+
protocolVersion: LATEST_MCP_PROTOCOL_VERSION,
|
|
214
|
+
supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
|
|
215
|
+
serverInfo,
|
|
216
|
+
instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
|
|
217
|
+
capabilities: {
|
|
218
|
+
tools: { listChanged: false },
|
|
219
|
+
resources: { listChanged: false, subscribe: false },
|
|
220
|
+
prompts: { listChanged: false },
|
|
221
|
+
completions: {},
|
|
222
|
+
extensions: {
|
|
223
|
+
"io.modelcontextprotocol/skills": {},
|
|
224
|
+
"io.modelcontextprotocol/apps": {},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
ttlMs: 300_000,
|
|
228
|
+
cacheScope: "public",
|
|
229
|
+
_meta: {
|
|
230
|
+
"io.modelcontextprotocol/protocolVersion": LATEST_MCP_PROTOCOL_VERSION,
|
|
231
|
+
"io.modelcontextprotocol/serverInfo": serverInfo,
|
|
232
|
+
"io.modelcontextprotocol/instructions": MUXO_MCP_SERVER_INSTRUCTIONS,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
export const MUXO_MCP_RESOURCE_TEMPLATES = [
|
|
237
|
+
{
|
|
238
|
+
uriTemplate: "muxo://tools/{slug}",
|
|
239
|
+
name: "Muxo Tool Specification & Governance Contract",
|
|
240
|
+
title: "Muxo Tool Specification & Governance Contract",
|
|
241
|
+
description: "Inspect a specific Muxo catalog tool's input JSON schema, risk classification, provider lane, and credit cost by slug (e.g. muxo://tools/instant.dns.lookup).",
|
|
242
|
+
mimeType: "application/json",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
uriTemplate: "muxo://plans/{planId}",
|
|
246
|
+
name: "Muxo Execution Plan Record",
|
|
247
|
+
title: "Muxo Execution Plan Record",
|
|
248
|
+
description: "Read a governed execution plan (plan_*), its approval status, and its Microsoft Governance DecisionRecord.",
|
|
249
|
+
mimeType: "application/json",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
uriTemplate: "muxo://runs/{runId}",
|
|
253
|
+
name: "Muxo Execution Run Record",
|
|
254
|
+
title: "Muxo Execution Run Record",
|
|
255
|
+
description: "Read an execution run (run_*), its output, duration, and metered cost.",
|
|
256
|
+
mimeType: "application/json",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
uriTemplate: "muxo://receipts/{receiptId}",
|
|
260
|
+
name: "Muxo Cryptographic Receipt",
|
|
261
|
+
title: "Muxo Cryptographic Receipt",
|
|
262
|
+
description: "Read and verify a signed HMAC-SHA256 + Ed25519 execution receipt (rcpt_* or 'latest').",
|
|
263
|
+
mimeType: "application/json",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
uriTemplate: "skill://muxo/{skillName}/SKILL.md",
|
|
267
|
+
name: "Muxo Agent Skill Markdown (SEP-2640)",
|
|
268
|
+
title: "Muxo Agent Skill Markdown (SEP-2640)",
|
|
269
|
+
description: "Read the SKILL.md entry point for a published Muxo Agent Skill ('muxo' or 'setup').",
|
|
270
|
+
mimeType: "text/markdown",
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
/**
|
|
274
|
+
* Evaluates MCP `completion/complete` requests for prompt arguments and resource templates.
|
|
275
|
+
*/
|
|
276
|
+
export function completeMcpArgument(params, toolSlugs = []) {
|
|
277
|
+
const argObj = params?.argument && typeof params.argument === "object" && !Array.isArray(params.argument)
|
|
278
|
+
? params.argument
|
|
279
|
+
: {};
|
|
280
|
+
const argName = String(argObj.name || "").trim();
|
|
281
|
+
const prefix = String(argObj.value || "").trim().toLowerCase();
|
|
282
|
+
let candidates = [];
|
|
283
|
+
if (argName === "skillName" || argName === "name") {
|
|
284
|
+
candidates = ["muxo", "setup"];
|
|
285
|
+
}
|
|
286
|
+
else if (argName === "focus") {
|
|
287
|
+
candidates = ["full", "pending_approvals", "billing", "receipts", "memory"];
|
|
288
|
+
}
|
|
289
|
+
else if (argName === "slug" || argName === "tool_slug") {
|
|
290
|
+
candidates =
|
|
291
|
+
toolSlugs.length > 0
|
|
292
|
+
? toolSlugs
|
|
293
|
+
: [
|
|
294
|
+
"instant.dns.lookup",
|
|
295
|
+
"instant.seo.audit",
|
|
296
|
+
"instant.web.search",
|
|
297
|
+
"app.github.create_issue",
|
|
298
|
+
"app.gmail.send_email",
|
|
299
|
+
"billing.stripe.create_refund",
|
|
300
|
+
"resource.turso.database",
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
else if (argName === "receiptId" || argName === "receipt_id") {
|
|
304
|
+
candidates = ["latest", "chain"];
|
|
305
|
+
}
|
|
306
|
+
const filtered = prefix
|
|
307
|
+
? candidates.filter((c) => c.toLowerCase().includes(prefix))
|
|
308
|
+
: candidates;
|
|
309
|
+
const values = filtered.slice(0, 50);
|
|
310
|
+
return {
|
|
311
|
+
values,
|
|
312
|
+
total: filtered.length,
|
|
313
|
+
hasMore: filtered.length > values.length,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
1
316
|
export const MCP_TOOLS_OAUTH_SCOPE = "mcp:tools";
|
|
2
317
|
/** Workspace-bound tools require an OAuth bearer token (or workspace API key). */
|
|
3
318
|
export const DEFAULT_TOOL_SECURITY_SCHEMES = [
|
|
@@ -14,12 +329,164 @@ export const PUBLIC_MCP_TOOL_NAMES = new Set([
|
|
|
14
329
|
"search",
|
|
15
330
|
"fetch",
|
|
16
331
|
]);
|
|
332
|
+
export const MUXO_UI_WIDGET_URI = "ui://widget/muxo-card.html";
|
|
333
|
+
export const MUXO_UI_MCP_APP_URI = "ui://widget/muxo-app.html";
|
|
334
|
+
/**
|
|
335
|
+
* Tools whose results represent interactive user-facing milestones (catalog carousel,
|
|
336
|
+
* OAuth handoff, action plan review/edit, execution receipt, or workspace summary).
|
|
337
|
+
* Pure data/inspection helpers remain usable without forcing unnecessary widget remounts.
|
|
338
|
+
*/
|
|
339
|
+
export const UI_RENDERED_MCP_TOOL_NAMES = new Set([
|
|
340
|
+
"get_profile",
|
|
341
|
+
"get_organization",
|
|
342
|
+
"search_tools",
|
|
343
|
+
"start_connection",
|
|
344
|
+
"plan_action",
|
|
345
|
+
"get_plan",
|
|
346
|
+
"execute_read",
|
|
347
|
+
"execute_write",
|
|
348
|
+
"execute_admin",
|
|
349
|
+
"execute_financial",
|
|
350
|
+
"execute_provisioning",
|
|
351
|
+
"execute_destructive",
|
|
352
|
+
"get_receipt",
|
|
353
|
+
]);
|
|
354
|
+
export function mcpToolUiMeta(invoking, invoked, extra) {
|
|
355
|
+
return {
|
|
356
|
+
"openai/outputTemplate": MUXO_UI_WIDGET_URI,
|
|
357
|
+
"openai/toolInvocation/invoking": invoking,
|
|
358
|
+
"openai/toolInvocation/invoked": invoked,
|
|
359
|
+
"openai/widgetAccessible": true,
|
|
360
|
+
"openai/visibility": "public",
|
|
361
|
+
ui: {
|
|
362
|
+
resourceUri: MUXO_UI_WIDGET_URI,
|
|
363
|
+
visibility: ["model", "app"],
|
|
364
|
+
},
|
|
365
|
+
...(extra || {}),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Extracts optional OpenAI / MCP Apps client hints from JSON-RPC `params._meta`
|
|
370
|
+
* (`openai/locale`, `openai/userAgent`, `openai/userLocation`, `openai/subject`,
|
|
371
|
+
* `openai/session`, `openai/organization`, `openai/widgetSessionId`).
|
|
372
|
+
* These are best-effort hints and must never be used for authorization decisions.
|
|
373
|
+
*/
|
|
374
|
+
export function extractOpenAiClientMeta(params) {
|
|
375
|
+
const rawMeta = params && typeof params._meta === "object" && params._meta !== null
|
|
376
|
+
? params._meta
|
|
377
|
+
: {};
|
|
378
|
+
const locale = typeof rawMeta["openai/locale"] === "string"
|
|
379
|
+
? rawMeta["openai/locale"]
|
|
380
|
+
: typeof rawMeta["webplus/i18n"] === "string"
|
|
381
|
+
? rawMeta["webplus/i18n"]
|
|
382
|
+
: undefined;
|
|
383
|
+
const userAgent = typeof rawMeta["openai/userAgent"] === "string"
|
|
384
|
+
? rawMeta["openai/userAgent"]
|
|
385
|
+
: undefined;
|
|
386
|
+
const userLocation = rawMeta["openai/userLocation"] &&
|
|
387
|
+
typeof rawMeta["openai/userLocation"] === "object" &&
|
|
388
|
+
!Array.isArray(rawMeta["openai/userLocation"])
|
|
389
|
+
? rawMeta["openai/userLocation"]
|
|
390
|
+
: undefined;
|
|
391
|
+
const subject = typeof rawMeta["openai/subject"] === "string"
|
|
392
|
+
? rawMeta["openai/subject"]
|
|
393
|
+
: undefined;
|
|
394
|
+
const session = typeof rawMeta["openai/session"] === "string"
|
|
395
|
+
? rawMeta["openai/session"]
|
|
396
|
+
: undefined;
|
|
397
|
+
const organization = typeof rawMeta["openai/organization"] === "string"
|
|
398
|
+
? rawMeta["openai/organization"]
|
|
399
|
+
: undefined;
|
|
400
|
+
const widgetSessionId = typeof rawMeta["openai/widgetSessionId"] === "string"
|
|
401
|
+
? rawMeta["openai/widgetSessionId"]
|
|
402
|
+
: undefined;
|
|
403
|
+
return {
|
|
404
|
+
...(locale ? { locale } : {}),
|
|
405
|
+
...(userAgent ? { userAgent } : {}),
|
|
406
|
+
...(userLocation ? { userLocation } : {}),
|
|
407
|
+
...(subject ? { subject } : {}),
|
|
408
|
+
...(session ? { session } : {}),
|
|
409
|
+
...(organization ? { organization } : {}),
|
|
410
|
+
...(widgetSessionId ? { widgetSessionId } : {}),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Enriches a tool descriptor for `tools/list` with both top-level `securitySchemes`
|
|
415
|
+
* and `_meta.securitySchemes` back-compat mirror per the OpenAI Plugin UI reference,
|
|
416
|
+
* as well as all four standard MCP tool annotations (`readOnlyHint`, `destructiveHint`,
|
|
417
|
+
* `idempotentHint`, `openWorldHint`).
|
|
418
|
+
*/
|
|
419
|
+
export function buildMcpToolDescriptor(tool) {
|
|
420
|
+
const schemes = securitySchemesForTool(tool.name);
|
|
421
|
+
const idempotentHint = typeof tool.annotations.idempotentHint === "boolean"
|
|
422
|
+
? tool.annotations.idempotentHint
|
|
423
|
+
: tool.annotations.readOnlyHint;
|
|
424
|
+
return {
|
|
425
|
+
...tool,
|
|
426
|
+
annotations: {
|
|
427
|
+
...tool.annotations,
|
|
428
|
+
title: tool.annotations.title || tool.title,
|
|
429
|
+
idempotentHint,
|
|
430
|
+
},
|
|
431
|
+
securitySchemes: schemes,
|
|
432
|
+
_meta: {
|
|
433
|
+
...(tool._meta || {}),
|
|
434
|
+
securitySchemes: schemes,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Builds the `_meta` object returned on `tools/call` results. Delivers widget-only
|
|
440
|
+
* metadata (`_meta`) without bloating the model-visible conversation transcript.
|
|
441
|
+
*/
|
|
442
|
+
export function buildMcpToolResultMeta(toolName, params) {
|
|
443
|
+
const clientMeta = extractOpenAiClientMeta(params);
|
|
444
|
+
const attachUi = UI_RENDERED_MCP_TOOL_NAMES.has(toolName);
|
|
445
|
+
return {
|
|
446
|
+
...(attachUi
|
|
447
|
+
? {
|
|
448
|
+
"openai/outputTemplate": MUXO_UI_WIDGET_URI,
|
|
449
|
+
ui: { resourceUri: MUXO_UI_WIDGET_URI },
|
|
450
|
+
}
|
|
451
|
+
: {}),
|
|
452
|
+
...(clientMeta.widgetSessionId
|
|
453
|
+
? { "openai/widgetSessionId": clientMeta.widgetSessionId }
|
|
454
|
+
: {}),
|
|
455
|
+
...(clientMeta.locale ? { "openai/locale": clientMeta.locale } : {}),
|
|
456
|
+
...(Object.keys(clientMeta).length > 0 ? { clientContext: clientMeta } : {}),
|
|
457
|
+
muxoWidget: {
|
|
458
|
+
toolName,
|
|
459
|
+
uiRendered: attachUi,
|
|
460
|
+
invokedAt: new Date().toISOString(),
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Builds the `_meta` object for error tool results, including RFC 7235
|
|
466
|
+
* `_meta["mcp/www_authenticate"]` when an authentication challenge is required.
|
|
467
|
+
*/
|
|
468
|
+
export function buildMcpErrorResultMeta(err, baseUrl = "https://api.muxo.ai") {
|
|
469
|
+
const cleanBase = String(baseUrl || "https://api.muxo.ai").replace(/\/+$/, "");
|
|
470
|
+
const statusCode = err?.statusCode;
|
|
471
|
+
const code = err?.code;
|
|
472
|
+
if (statusCode === 401 ||
|
|
473
|
+
code === "auth_required" ||
|
|
474
|
+
code === "unauthorized") {
|
|
475
|
+
return {
|
|
476
|
+
"mcp/www_authenticate": [
|
|
477
|
+
`Bearer resource_metadata="${cleanBase}/.well-known/oauth-protected-resource/mcp", scope="${MCP_TOOLS_OAUTH_SCOPE}"`,
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
17
483
|
/** Stable per-workspace agent identity returned at agent signup on both runtimes. */
|
|
18
484
|
export function workspaceAgentDid(workspaceId) {
|
|
19
485
|
return `did:agent:muxo:${workspaceId}`;
|
|
20
486
|
}
|
|
21
487
|
/** JSON-RPC methods that anonymous MCP clients may call for discovery. */
|
|
22
488
|
export const PUBLIC_MCP_METHODS = new Set([
|
|
489
|
+
"server/discover",
|
|
23
490
|
"initialize",
|
|
24
491
|
"ping",
|
|
25
492
|
"tools/list",
|
|
@@ -27,6 +494,10 @@ export const PUBLIC_MCP_METHODS = new Set([
|
|
|
27
494
|
"resources/read",
|
|
28
495
|
"resources/templates/list",
|
|
29
496
|
"prompts/list",
|
|
497
|
+
"completion/complete",
|
|
498
|
+
"subscriptions/listen",
|
|
499
|
+
"skills/list",
|
|
500
|
+
"skills/get",
|
|
30
501
|
]);
|
|
31
502
|
/** True when a single JSON-RPC MCP request only needs public discovery access. */
|
|
32
503
|
export function isPublicMcpRequest(rpc) {
|
|
@@ -107,6 +578,9 @@ export const GET_RUN_CONTEXT_ACTIONS = [
|
|
|
107
578
|
"forget",
|
|
108
579
|
"memory_graph",
|
|
109
580
|
"set_guardrail",
|
|
581
|
+
"reset_guardrails",
|
|
582
|
+
"check_policy",
|
|
583
|
+
"kill_switch",
|
|
110
584
|
"governance_status",
|
|
111
585
|
"scan_context",
|
|
112
586
|
"list_workspaces",
|
|
@@ -153,6 +627,51 @@ export function unknownToolError(slug) {
|
|
|
153
627
|
export function buildApprovalUrl(baseUrl, planId) {
|
|
154
628
|
return `${baseUrl.replace(/\/$/, "")}/approve/${encodeURIComponent(planId)}`;
|
|
155
629
|
}
|
|
630
|
+
export function buildApprovalHumanHandoff(baseUrl, plan) {
|
|
631
|
+
const approvalUrl = buildApprovalUrl(baseUrl, plan.id);
|
|
632
|
+
const ctaLabel = "Review & approve";
|
|
633
|
+
const headline = `Muxo needs your approval to run ${plan.toolSlug}`;
|
|
634
|
+
const instructions = `Open ${approvalUrl} to review and approve this ${plan.riskLevel}-risk action with one click (or run \`muxo approve ${plan.id}\`). Nothing runs until you approve.`;
|
|
635
|
+
const markdownForUser = `### Muxo Approval Required\n**[${ctaLabel} in Muxo](${approvalUrl})**\n\nPlan \`${plan.id}\` (\`${plan.toolSlug}\` · **${plan.riskLevel} risk**) is held by your Muxo workspace guardrails. Open the link above to review and approve it in one click (or run \`muxo approve ${plan.id}\`). Nothing executes until you approve.`;
|
|
636
|
+
return {
|
|
637
|
+
kind: "approval",
|
|
638
|
+
brand: "Muxo",
|
|
639
|
+
url: approvalUrl,
|
|
640
|
+
ctaLabel,
|
|
641
|
+
headline,
|
|
642
|
+
instructions,
|
|
643
|
+
markdownForUser,
|
|
644
|
+
planId: plan.id,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
export function buildOAuthHumanHandoff(params) {
|
|
648
|
+
const slug = String(params.providerSlug || "app")
|
|
649
|
+
.trim()
|
|
650
|
+
.toLowerCase()
|
|
651
|
+
.replace(/^(app\.|resource\.)/, "");
|
|
652
|
+
const name = params.providerName ||
|
|
653
|
+
slug.charAt(0).toUpperCase() + slug.slice(1);
|
|
654
|
+
const url = params.redirectUrl || null;
|
|
655
|
+
const ctaLabel = `Connect ${name}`;
|
|
656
|
+
const headline = `Connect ${name} to continue`;
|
|
657
|
+
const instructions = params.instructions ||
|
|
658
|
+
(url
|
|
659
|
+
? `Open ${url} in your browser to authorize ${name}, then call wait_for_connection({ provider: "${slug}" }).`
|
|
660
|
+
: `Call start_connection({ provider: "${slug}" }) to generate a Muxo authorization link for ${name}.`);
|
|
661
|
+
const markdownForUser = url
|
|
662
|
+
? `### Connect ${name} with Muxo\n**[${ctaLabel} in Muxo](${url})**\n\nAuthorize **${name}** in your browser via **Muxo Connected Apps**. Your credentials are sealed in your isolated Muxo workspace vault and never exposed to the AI model. Once connected, tell me to continue.`
|
|
663
|
+
: `### Connect ${name} with Muxo\nAuthorization is required for **${name}** via **Muxo Connected Apps**.`;
|
|
664
|
+
return {
|
|
665
|
+
kind: "oauth",
|
|
666
|
+
brand: "Muxo",
|
|
667
|
+
url,
|
|
668
|
+
ctaLabel,
|
|
669
|
+
headline,
|
|
670
|
+
instructions,
|
|
671
|
+
markdownForUser,
|
|
672
|
+
provider: slug,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
156
675
|
/** Structured response returned when execute_<risk> is called on a plan still awaiting approval. */
|
|
157
676
|
export function buildApprovalRequiredPayload(baseUrl, plan) {
|
|
158
677
|
const approvalUrl = buildApprovalUrl(baseUrl, plan.id);
|
|
@@ -163,14 +682,7 @@ export function buildApprovalRequiredPayload(baseUrl, plan) {
|
|
|
163
682
|
riskLevel: plan.riskLevel,
|
|
164
683
|
approvalStatus: plan.approvalStatus,
|
|
165
684
|
approvalUrl,
|
|
166
|
-
humanHandoff:
|
|
167
|
-
kind: "approval",
|
|
168
|
-
url: approvalUrl,
|
|
169
|
-
ctaLabel: "Review & approve",
|
|
170
|
-
headline: `Muxo needs your approval to run ${plan.toolSlug}`,
|
|
171
|
-
instructions: `Open ${approvalUrl} to review and approve this ${plan.riskLevel}-risk action with one click (or run \`muxo approve ${plan.id}\`). Nothing runs until you approve.`,
|
|
172
|
-
planId: plan.id,
|
|
173
|
-
},
|
|
685
|
+
humanHandoff: buildApprovalHumanHandoff(baseUrl, plan),
|
|
174
686
|
polling: {
|
|
175
687
|
tool: "get_plan",
|
|
176
688
|
input: { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 30 },
|
|
@@ -304,9 +816,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
304
816
|
destructiveHint: false,
|
|
305
817
|
openWorldHint: false,
|
|
306
818
|
},
|
|
307
|
-
_meta: {
|
|
308
|
-
"openai/profile": true,
|
|
309
|
-
},
|
|
819
|
+
_meta: mcpToolUiMeta("Loading Muxo workspace profile…", "Loaded workspace profile", { "openai/profile": true }),
|
|
310
820
|
},
|
|
311
821
|
{
|
|
312
822
|
name: "get_organization",
|
|
@@ -322,6 +832,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
322
832
|
destructiveHint: false,
|
|
323
833
|
openWorldHint: false,
|
|
324
834
|
},
|
|
835
|
+
_meta: mcpToolUiMeta("Loading organization & guardrails summary…", "Loaded organization summary"),
|
|
325
836
|
},
|
|
326
837
|
{
|
|
327
838
|
name: "search_tools",
|
|
@@ -359,6 +870,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
359
870
|
destructiveHint: false,
|
|
360
871
|
openWorldHint: true,
|
|
361
872
|
},
|
|
873
|
+
_meta: mcpToolUiMeta("Searching Muxo tool catalog…", "Found matching tools"),
|
|
362
874
|
},
|
|
363
875
|
{
|
|
364
876
|
name: "inspect_tool",
|
|
@@ -380,6 +892,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
380
892
|
destructiveHint: false,
|
|
381
893
|
openWorldHint: false,
|
|
382
894
|
},
|
|
895
|
+
_meta: mcpToolUiMeta("Inspecting tool schema & risk lane…", "Inspected tool schema"),
|
|
383
896
|
},
|
|
384
897
|
{
|
|
385
898
|
name: "connection_status",
|
|
@@ -405,23 +918,24 @@ export const MUXO_MCP_TOOLS = [
|
|
|
405
918
|
destructiveHint: false,
|
|
406
919
|
openWorldHint: true,
|
|
407
920
|
},
|
|
921
|
+
_meta: mcpToolUiMeta("Checking provider connection status…", "Checked connection status"),
|
|
408
922
|
},
|
|
409
923
|
{
|
|
410
924
|
name: "start_connection",
|
|
411
925
|
title: "Start OAuth Connection Handoff",
|
|
412
|
-
description: "Initiate a managed OAuth connection handoff for a Connected App
|
|
926
|
+
description: "Initiate a managed OAuth connection handoff for a Connected SaaS App (e.g. GitHub, Gmail, Slack, Notion, Linear). Cloud Infrastructure & Hosting providers (vercel, cloudflare, netlify, render, herenow, turso, neon, supabase, upstash, resend, etc.) are already connected via Stripe Projects (stripe_projects) and do NOT require start_connection or OAuth — use resource.<provider>.* with plan_action and execute_provisioning directly.",
|
|
413
927
|
inputSchema: {
|
|
414
928
|
type: "object",
|
|
415
929
|
required: ["provider"],
|
|
416
930
|
properties: {
|
|
417
931
|
provider: {
|
|
418
932
|
type: "string",
|
|
419
|
-
description: "
|
|
933
|
+
description: "Connected SaaS App slug (e.g. 'github', 'gmail', 'slack', 'notion', 'linear'). For Cloud Infrastructure (vercel, netlify, cloudflare, turso, neon, supabase, etc.), no OAuth connection is needed.",
|
|
420
934
|
},
|
|
421
935
|
lane: {
|
|
422
936
|
type: "string",
|
|
423
937
|
enum: ["composio_sdk", "stripe_projects"],
|
|
424
|
-
description: "Provider lane (
|
|
938
|
+
description: "Provider lane (Cloud Infrastructure providers automatically route to stripe_projects with zero OAuth; SaaS apps use composio_sdk).",
|
|
425
939
|
},
|
|
426
940
|
callback_url: {
|
|
427
941
|
type: "string",
|
|
@@ -435,6 +949,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
435
949
|
destructiveHint: false,
|
|
436
950
|
openWorldHint: true,
|
|
437
951
|
},
|
|
952
|
+
_meta: mcpToolUiMeta("Preparing OAuth connection handoff…", "Prepared OAuth connection link"),
|
|
438
953
|
},
|
|
439
954
|
{
|
|
440
955
|
name: "wait_for_connection",
|
|
@@ -461,6 +976,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
461
976
|
destructiveHint: false,
|
|
462
977
|
openWorldHint: true,
|
|
463
978
|
},
|
|
979
|
+
_meta: mcpToolUiMeta("Verifying OAuth connection status…", "Verified OAuth connection status"),
|
|
464
980
|
},
|
|
465
981
|
{
|
|
466
982
|
name: "plan_action",
|
|
@@ -490,6 +1006,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
490
1006
|
destructiveHint: false,
|
|
491
1007
|
openWorldHint: false,
|
|
492
1008
|
},
|
|
1009
|
+
_meta: mcpToolUiMeta("Evaluating guardrails & creating action plan…", "Created governed action plan"),
|
|
493
1010
|
},
|
|
494
1011
|
{
|
|
495
1012
|
name: "get_plan",
|
|
@@ -516,6 +1033,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
516
1033
|
destructiveHint: false,
|
|
517
1034
|
openWorldHint: false,
|
|
518
1035
|
},
|
|
1036
|
+
_meta: mcpToolUiMeta("Checking action plan status…", "Loaded action plan status"),
|
|
519
1037
|
},
|
|
520
1038
|
{
|
|
521
1039
|
name: "get_run_context",
|
|
@@ -537,17 +1055,32 @@ export const MUXO_MCP_TOOLS = [
|
|
|
537
1055
|
"forget",
|
|
538
1056
|
"memory_graph",
|
|
539
1057
|
"set_guardrail",
|
|
1058
|
+
"reset_guardrails",
|
|
1059
|
+
"check_policy",
|
|
1060
|
+
"kill_switch",
|
|
540
1061
|
"governance_status",
|
|
541
1062
|
"scan_context",
|
|
542
1063
|
"list_workspaces",
|
|
543
1064
|
"switch_workspace",
|
|
544
1065
|
"onboard_team",
|
|
545
1066
|
],
|
|
546
|
-
description: "Control-plane operation: 'inspect' (default), 'remember' (save fact/preference), 'recall' (list memories), 'forget' (delete matching memories), 'memory_graph' (bi-temporal entity/relationship lineage), 'set_guardrail' (apply plain-English or structured parameter_rules guardrail), 'governance_status', 'scan_context' (OWASP ASI-06 check), 'list_workspaces', 'switch_workspace', or 'onboard_team'",
|
|
1067
|
+
description: "Control-plane operation: 'inspect' (default), 'remember' (save fact/preference), 'recall' (list memories), 'forget' (delete matching memories), 'memory_graph' (bi-temporal entity/relationship lineage), 'set_guardrail' (apply plain-English or structured parameter_rules guardrail), 'reset_guardrails' (restore default safe guardrails), 'check_policy' (dry-run evaluate tool_slug + input against policy and parameter guardrails before planning), 'kill_switch' (pause/resume agent execution), 'governance_status' (Microsoft 9-module status + LLM policy brief), 'scan_context' (OWASP ASI-06 check), 'list_workspaces', 'switch_workspace', or 'onboard_team'",
|
|
547
1068
|
},
|
|
548
1069
|
prompt: {
|
|
549
1070
|
type: "string",
|
|
550
|
-
description: "Natural-language instruction when action is 'remember', 'set_guardrail', or 'scan_context' (e.g. 'Only allow sending emails to @muxo.ai' or 'Require approval for Stripe refunds over $50, auto-approve under $50')",
|
|
1071
|
+
description: "Natural-language instruction when action is 'remember', 'set_guardrail', 'kill_switch', or 'scan_context' (e.g. 'Only allow sending emails to @muxo.ai' or 'Require approval for Stripe refunds over $50, auto-approve under $50')",
|
|
1072
|
+
},
|
|
1073
|
+
tool_slug: {
|
|
1074
|
+
type: "string",
|
|
1075
|
+
description: "Tool slug to dry-run evaluate when action='check_policy' (e.g. 'app.gmail.send_email', 'billing.stripe.create_refund')",
|
|
1076
|
+
},
|
|
1077
|
+
input: {
|
|
1078
|
+
type: "object",
|
|
1079
|
+
description: "Tool arguments object to dry-run evaluate against parameter guardrails and learned defaults when action='check_policy'",
|
|
1080
|
+
},
|
|
1081
|
+
enabled: {
|
|
1082
|
+
type: "boolean",
|
|
1083
|
+
description: "When action='kill_switch', set true to pause all workspace agent execution or false to resume execution",
|
|
551
1084
|
},
|
|
552
1085
|
parameter_rules: {
|
|
553
1086
|
type: "array",
|
|
@@ -644,6 +1177,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
644
1177
|
destructiveHint: false,
|
|
645
1178
|
openWorldHint: false,
|
|
646
1179
|
},
|
|
1180
|
+
_meta: mcpToolUiMeta("Loading workspace context, memory & guardrails…", "Loaded workspace context & guardrails"),
|
|
647
1181
|
},
|
|
648
1182
|
{
|
|
649
1183
|
name: "execute_read",
|
|
@@ -662,6 +1196,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
662
1196
|
destructiveHint: false,
|
|
663
1197
|
openWorldHint: true,
|
|
664
1198
|
},
|
|
1199
|
+
_meta: mcpToolUiMeta("Executing governed read operation…", "Completed read operation"),
|
|
665
1200
|
},
|
|
666
1201
|
{
|
|
667
1202
|
name: "execute_write",
|
|
@@ -680,6 +1215,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
680
1215
|
destructiveHint: false,
|
|
681
1216
|
openWorldHint: true,
|
|
682
1217
|
},
|
|
1218
|
+
_meta: mcpToolUiMeta("Executing governed write operation…", "Completed write operation"),
|
|
683
1219
|
},
|
|
684
1220
|
{
|
|
685
1221
|
name: "execute_admin",
|
|
@@ -698,6 +1234,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
698
1234
|
destructiveHint: false,
|
|
699
1235
|
openWorldHint: true,
|
|
700
1236
|
},
|
|
1237
|
+
_meta: mcpToolUiMeta("Executing governed admin operation…", "Completed admin operation"),
|
|
701
1238
|
},
|
|
702
1239
|
{
|
|
703
1240
|
name: "execute_financial",
|
|
@@ -716,6 +1253,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
716
1253
|
destructiveHint: false,
|
|
717
1254
|
openWorldHint: true,
|
|
718
1255
|
},
|
|
1256
|
+
_meta: mcpToolUiMeta("Executing governed financial operation…", "Completed financial operation"),
|
|
719
1257
|
},
|
|
720
1258
|
{
|
|
721
1259
|
name: "execute_provisioning",
|
|
@@ -734,6 +1272,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
734
1272
|
destructiveHint: false,
|
|
735
1273
|
openWorldHint: true,
|
|
736
1274
|
},
|
|
1275
|
+
_meta: mcpToolUiMeta("Provisioning cloud infrastructure via Stripe Projects…", "Provisioned cloud resource"),
|
|
737
1276
|
},
|
|
738
1277
|
{
|
|
739
1278
|
name: "execute_destructive",
|
|
@@ -752,6 +1291,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
752
1291
|
destructiveHint: true,
|
|
753
1292
|
openWorldHint: true,
|
|
754
1293
|
},
|
|
1294
|
+
_meta: mcpToolUiMeta("Executing approved destructive operation…", "Completed destructive operation"),
|
|
755
1295
|
},
|
|
756
1296
|
{
|
|
757
1297
|
name: "get_run",
|
|
@@ -770,6 +1310,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
770
1310
|
destructiveHint: false,
|
|
771
1311
|
openWorldHint: false,
|
|
772
1312
|
},
|
|
1313
|
+
_meta: mcpToolUiMeta("Loading execution run details…", "Loaded execution run"),
|
|
773
1314
|
},
|
|
774
1315
|
{
|
|
775
1316
|
name: "cancel_run",
|
|
@@ -792,6 +1333,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
792
1333
|
destructiveHint: true,
|
|
793
1334
|
openWorldHint: false,
|
|
794
1335
|
},
|
|
1336
|
+
_meta: mcpToolUiMeta("Cancelling execution run…", "Cancelled execution run"),
|
|
795
1337
|
},
|
|
796
1338
|
{
|
|
797
1339
|
name: "get_receipt",
|
|
@@ -820,6 +1362,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
820
1362
|
destructiveHint: false,
|
|
821
1363
|
openWorldHint: false,
|
|
822
1364
|
},
|
|
1365
|
+
_meta: mcpToolUiMeta("Verifying Ed25519 & SHA-256 execution receipt…", "Verified cryptographic receipt"),
|
|
823
1366
|
},
|
|
824
1367
|
];
|
|
825
1368
|
/**
|
|
@@ -943,11 +1486,40 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
943
1486
|
break;
|
|
944
1487
|
}
|
|
945
1488
|
case "start_connection": {
|
|
946
|
-
const
|
|
1489
|
+
const conn = obj.connection && typeof obj.connection === "object" ? obj.connection : obj;
|
|
1490
|
+
const provider = String(conn.providerSlug || obj.providerSlug || toolArgs.provider || "<provider>");
|
|
1491
|
+
const providerName = String(conn.providerName || obj.providerName || provider);
|
|
1492
|
+
const redirectUrl = (conn.redirectUrl ?? obj.redirectUrl ?? null);
|
|
1493
|
+
const isStripeProjects = conn.lane === "stripe_projects" || isStripeProjectsExclusiveProvider(provider);
|
|
1494
|
+
if (isStripeProjects) {
|
|
1495
|
+
const canonicalSlug = resolveStripeProjectsCanonicalToolSlug(provider) || `resource.${provider}.project`;
|
|
1496
|
+
guidance = {
|
|
1497
|
+
summary: `${providerName} (${provider}) is connected via Stripe Projects Cloud Infrastructure (zero OAuth handoff required). Call plan_action with ${canonicalSlug} and execute_provisioning directly.`,
|
|
1498
|
+
nextToolCalls: [
|
|
1499
|
+
{
|
|
1500
|
+
tool: "plan_action",
|
|
1501
|
+
input: {
|
|
1502
|
+
tool_slug: canonicalSlug,
|
|
1503
|
+
input: { resource_name: `${provider}-service`, plan: "free" },
|
|
1504
|
+
reason: `Provision ${providerName} via Stripe Projects`,
|
|
1505
|
+
},
|
|
1506
|
+
when: "Immediately; Stripe Projects providers do not require OAuth handoff",
|
|
1507
|
+
},
|
|
1508
|
+
],
|
|
1509
|
+
};
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
if (redirectUrl && !obj.humanHandoff) {
|
|
1513
|
+
obj.humanHandoff = buildOAuthHumanHandoff({
|
|
1514
|
+
providerSlug: provider,
|
|
1515
|
+
providerName,
|
|
1516
|
+
redirectUrl,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
947
1519
|
guidance = {
|
|
948
|
-
summary:
|
|
949
|
-
? `Share ${String(
|
|
950
|
-
: `Connection status for ${provider}: ${String(obj.status || "active")}.`,
|
|
1520
|
+
summary: redirectUrl
|
|
1521
|
+
? `Share ${String(redirectUrl)} with the user so they can authorize ${provider} with Muxo, then call wait_for_connection.`
|
|
1522
|
+
: `Connection status for ${provider}: ${String(conn.status || obj.status || "active")}.`,
|
|
951
1523
|
nextToolCalls: [
|
|
952
1524
|
{
|
|
953
1525
|
tool: "wait_for_connection",
|
|
@@ -960,9 +1532,37 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
960
1532
|
}
|
|
961
1533
|
case "wait_for_connection": {
|
|
962
1534
|
const provider = String(obj.provider || toolArgs.provider || "<provider>");
|
|
1535
|
+
const redirectUrl = (obj.redirectUrl ?? obj.connection?.redirectUrl ?? null);
|
|
1536
|
+
const isStripeProjects = obj.connection?.lane === "stripe_projects" ||
|
|
1537
|
+
isStripeProjectsExclusiveProvider(provider);
|
|
1538
|
+
if (isStripeProjects) {
|
|
1539
|
+
const canonicalSlug = resolveStripeProjectsCanonicalToolSlug(provider) || `resource.${provider}.project`;
|
|
1540
|
+
guidance = {
|
|
1541
|
+
summary: `${provider} is connected and active via Stripe Projects Cloud Infrastructure (zero OAuth required). Proceed to plan_action with ${canonicalSlug} and execute_provisioning.`,
|
|
1542
|
+
nextToolCalls: [
|
|
1543
|
+
{
|
|
1544
|
+
tool: "plan_action",
|
|
1545
|
+
input: {
|
|
1546
|
+
tool_slug: canonicalSlug,
|
|
1547
|
+
input: { resource_name: `${provider}-service`, plan: "free" },
|
|
1548
|
+
reason: `Provision ${provider} via Stripe Projects`,
|
|
1549
|
+
},
|
|
1550
|
+
when: "Now; Stripe Projects provider is active",
|
|
1551
|
+
},
|
|
1552
|
+
],
|
|
1553
|
+
};
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
if (!obj.connected && redirectUrl && !obj.humanHandoff) {
|
|
1557
|
+
obj.humanHandoff = buildOAuthHumanHandoff({
|
|
1558
|
+
providerSlug: provider,
|
|
1559
|
+
providerName: obj.connection?.providerName,
|
|
1560
|
+
redirectUrl,
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
963
1563
|
guidance = obj.connected
|
|
964
1564
|
? {
|
|
965
|
-
summary: `${provider} is connected and active. Proceed to plan_action or execute_<riskLevel>.`,
|
|
1565
|
+
summary: `${provider} is connected and active in Muxo. Proceed to plan_action or execute_<riskLevel>.`,
|
|
966
1566
|
nextToolCalls: [
|
|
967
1567
|
{
|
|
968
1568
|
tool: "plan_action",
|
|
@@ -976,7 +1576,7 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
976
1576
|
],
|
|
977
1577
|
}
|
|
978
1578
|
: {
|
|
979
|
-
summary: `Still awaiting user OAuth completion for ${provider}. Ask the user to open ${String(
|
|
1579
|
+
summary: `Still awaiting user OAuth completion for ${provider}. Ask the user to open ${String(redirectUrl || "the authorization URL")} and call wait_for_connection again.`,
|
|
980
1580
|
nextToolCalls: [
|
|
981
1581
|
{
|
|
982
1582
|
tool: "wait_for_connection",
|
|
@@ -1020,6 +1620,80 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1020
1620
|
};
|
|
1021
1621
|
break;
|
|
1022
1622
|
}
|
|
1623
|
+
case "plan_action": {
|
|
1624
|
+
const plan = obj.plan || obj;
|
|
1625
|
+
const planId = String(plan?.id || obj.id || "<plan_id>");
|
|
1626
|
+
const risk = String(plan?.riskLevel || obj.riskLevel || "read");
|
|
1627
|
+
const status = String(plan?.approvalStatus || obj.approvalStatus || "auto_approved");
|
|
1628
|
+
const authHandoff = plan?.authHandoff || obj.authHandoff;
|
|
1629
|
+
if (authHandoff?.required) {
|
|
1630
|
+
const provider = String(authHandoff.providerSlug || "app");
|
|
1631
|
+
guidance = {
|
|
1632
|
+
summary: `Plan ${planId} requires OAuth connection for ${provider}. Share ${String(authHandoff.redirectUrl || "the connection URL")} with the user, call wait_for_connection, then execute_${risk}.`,
|
|
1633
|
+
nextToolCalls: [
|
|
1634
|
+
{
|
|
1635
|
+
tool: "wait_for_connection",
|
|
1636
|
+
input: { provider, timeout_seconds: 30 },
|
|
1637
|
+
when: "While the user completes the browser OAuth flow",
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
tool: `execute_${risk}`,
|
|
1641
|
+
input: { plan_id: planId },
|
|
1642
|
+
when: "After OAuth connection is active and plan is approved",
|
|
1643
|
+
},
|
|
1644
|
+
],
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
else if (status === "approved" || status === "auto_approved") {
|
|
1648
|
+
guidance = {
|
|
1649
|
+
summary: `Plan ${planId} is ${status} under workspace policy. Call execute_${risk} to execute it now.`,
|
|
1650
|
+
nextToolCalls: [
|
|
1651
|
+
{
|
|
1652
|
+
tool: `execute_${risk}`,
|
|
1653
|
+
input: { plan_id: planId },
|
|
1654
|
+
when: "Immediately; plan passed workspace guardrails",
|
|
1655
|
+
},
|
|
1656
|
+
],
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
else if (status === "pending_approval") {
|
|
1660
|
+
const approvalUrl = obj.humanHandoff?.url ||
|
|
1661
|
+
plan?.humanHandoff?.url ||
|
|
1662
|
+
`/approve/${planId}`;
|
|
1663
|
+
guidance = {
|
|
1664
|
+
summary: `Plan ${planId} (${risk} risk) requires human owner approval under active workspace guardrails. Share ${approvalUrl} with the user and poll get_plan(wait_for_approval: true).`,
|
|
1665
|
+
nextToolCalls: [
|
|
1666
|
+
{
|
|
1667
|
+
tool: "get_plan",
|
|
1668
|
+
input: {
|
|
1669
|
+
plan_id: planId,
|
|
1670
|
+
wait_for_approval: true,
|
|
1671
|
+
timeout_seconds: 25,
|
|
1672
|
+
},
|
|
1673
|
+
when: "After sharing the approval link with the user",
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
tool: `execute_${risk}`,
|
|
1677
|
+
input: { plan_id: planId },
|
|
1678
|
+
when: "Once get_plan reports approvalStatus='approved'",
|
|
1679
|
+
},
|
|
1680
|
+
],
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
guidance = {
|
|
1685
|
+
summary: `Plan ${planId} was ${status} by workspace policy (${String(plan?.policyReason || obj.policyReason || "guardrail rule")}). Call get_run_context({ action: 'governance_status' }) to inspect or adjust guardrails.`,
|
|
1686
|
+
nextToolCalls: [
|
|
1687
|
+
{
|
|
1688
|
+
tool: "get_run_context",
|
|
1689
|
+
input: { action: "governance_status" },
|
|
1690
|
+
when: "To inspect active workspace guardrails and parameter rules",
|
|
1691
|
+
},
|
|
1692
|
+
],
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1023
1697
|
case "execute_read":
|
|
1024
1698
|
case "execute_write":
|
|
1025
1699
|
case "execute_admin":
|
|
@@ -1041,26 +1715,44 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1041
1715
|
}
|
|
1042
1716
|
case "get_run_context":
|
|
1043
1717
|
case "set_guardrail":
|
|
1718
|
+
case "reset_guardrails":
|
|
1719
|
+
case "check_policy":
|
|
1720
|
+
case "kill_switch":
|
|
1721
|
+
case "governance_status":
|
|
1044
1722
|
case "configure_guardrails": {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1723
|
+
const act = String(obj.action || toolArgs.action || toolName || "inspect");
|
|
1724
|
+
if (act === "check_policy" && Array.isArray(obj.nextToolCalls) && obj.nextToolCalls.length > 0) {
|
|
1725
|
+
guidance = {
|
|
1726
|
+
summary: String(obj.remediationForLlm ||
|
|
1727
|
+
`Policy simulation complete: decision=${String(obj.decision || "auto_approved")}.`),
|
|
1728
|
+
nextToolCalls: obj.nextToolCalls,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
guidance = {
|
|
1733
|
+
summary: "Workspace context, memory defaults, and guardrails loaded/updated. Use get_run_context({ action: 'check_policy', tool_slug, input }) to dry-run test policy compliance, or plan_action to propose governed actions.",
|
|
1734
|
+
nextToolCalls: [
|
|
1735
|
+
{
|
|
1736
|
+
tool: "get_run_context",
|
|
1737
|
+
input: {
|
|
1738
|
+
action: "check_policy",
|
|
1739
|
+
tool_slug: "instant.web.search",
|
|
1740
|
+
input: { query: "example" },
|
|
1741
|
+
},
|
|
1742
|
+
when: "To dry-run check if a tool + input will pass active guardrails before creating a plan",
|
|
1059
1743
|
},
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1744
|
+
{
|
|
1745
|
+
tool: "plan_action",
|
|
1746
|
+
input: {
|
|
1747
|
+
tool_slug: "<tool_slug>",
|
|
1748
|
+
input: {},
|
|
1749
|
+
reason: "Planned action under active workspace guardrails",
|
|
1750
|
+
},
|
|
1751
|
+
when: "To evaluate guardrails and prepare an action for execution",
|
|
1752
|
+
},
|
|
1753
|
+
],
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1064
1756
|
break;
|
|
1065
1757
|
}
|
|
1066
1758
|
default:
|
|
@@ -1074,3 +1766,794 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1074
1766
|
}
|
|
1075
1767
|
return output;
|
|
1076
1768
|
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Canonical OpenAI Plugin UI (`text/html+skybridge`) & MCP Apps (`text/html;profile=mcp-app`)
|
|
1771
|
+
* widget resource descriptors exposed by `resources/list` and `resources/read`.
|
|
1772
|
+
* Follows:
|
|
1773
|
+
* - https://developers.openai.com/plugins/reference
|
|
1774
|
+
* - https://developers.openai.com/plugins/build/chatgpt-ui
|
|
1775
|
+
* - https://developers.openai.com/plugins/concepts/ui-guidelines
|
|
1776
|
+
*/
|
|
1777
|
+
export const MUXO_UI_CSP_DOMAINS = [
|
|
1778
|
+
"https://api.muxo.ai",
|
|
1779
|
+
"https://muxo.ai",
|
|
1780
|
+
"https://app.muxo.ai",
|
|
1781
|
+
];
|
|
1782
|
+
export const MUXO_UI_REDIRECT_DOMAINS = [
|
|
1783
|
+
"https://api.muxo.ai",
|
|
1784
|
+
"https://muxo.ai",
|
|
1785
|
+
"https://app.muxo.ai",
|
|
1786
|
+
"https://connect.composio.dev",
|
|
1787
|
+
"https://backend.composio.dev",
|
|
1788
|
+
];
|
|
1789
|
+
function buildMuxoUiResourceMeta(description) {
|
|
1790
|
+
return {
|
|
1791
|
+
ui: {
|
|
1792
|
+
prefersBorder: true,
|
|
1793
|
+
domain: "https://api.muxo.ai",
|
|
1794
|
+
csp: {
|
|
1795
|
+
connectDomains: [...MUXO_UI_CSP_DOMAINS],
|
|
1796
|
+
resourceDomains: [...MUXO_UI_CSP_DOMAINS],
|
|
1797
|
+
},
|
|
1798
|
+
},
|
|
1799
|
+
"openai/ui": {
|
|
1800
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1801
|
+
},
|
|
1802
|
+
"openai/widgetDescription": description,
|
|
1803
|
+
"openai/widgetPrefersBorder": true,
|
|
1804
|
+
"openai/widgetDomain": "https://api.muxo.ai",
|
|
1805
|
+
"openai/widgetCSP": {
|
|
1806
|
+
connect_domains: [...MUXO_UI_CSP_DOMAINS],
|
|
1807
|
+
resource_domains: [...MUXO_UI_CSP_DOMAINS],
|
|
1808
|
+
redirect_domains: [...MUXO_UI_REDIRECT_DOMAINS],
|
|
1809
|
+
},
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
export const MUXO_UI_RESOURCES = [
|
|
1813
|
+
{
|
|
1814
|
+
uri: MUXO_UI_WIDGET_URI,
|
|
1815
|
+
name: "Muxo Action, Catalog & Receipt Card",
|
|
1816
|
+
title: "Muxo Action, Catalog & Receipt Card",
|
|
1817
|
+
description: "Interactive ChatGPT inline card, tool catalog carousel, OAuth/approval handoff CTA, and fullscreen cryptographic receipt & SEO inspector.",
|
|
1818
|
+
mimeType: "text/html+skybridge",
|
|
1819
|
+
_meta: buildMuxoUiResourceMeta("Renders an inline card, 3-8 item tool carousel, or fullscreen inspector for Muxo action plans, OAuth connections, cloud provisioning, and Ed25519 receipts."),
|
|
1820
|
+
},
|
|
1821
|
+
{
|
|
1822
|
+
uri: MUXO_UI_MCP_APP_URI,
|
|
1823
|
+
name: "Muxo MCP App Widget",
|
|
1824
|
+
title: "Muxo MCP App Widget",
|
|
1825
|
+
description: "Standard MCP App HTML widget (text/html;profile=mcp-app) for Muxo plans, tool discovery carousels, and cryptographic receipt verification.",
|
|
1826
|
+
mimeType: "text/html;profile=mcp-app",
|
|
1827
|
+
_meta: buildMuxoUiResourceMeta("Standard MCP App HTML widget for Muxo action plans, OAuth handoffs, tool catalog carousels, and receipt verification."),
|
|
1828
|
+
},
|
|
1829
|
+
];
|
|
1830
|
+
/**
|
|
1831
|
+
* Renders the self-contained OpenAI Plugin UI / MCP App widget HTML (`ui://widget/muxo-card.html`).
|
|
1832
|
+
* Strictly adheres to https://developers.openai.com/plugins/concepts/ui-guidelines:
|
|
1833
|
+
* - System font stack (`-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif`)
|
|
1834
|
+
* - Light & dark mode CSS variables (`window.openai.theme` + `prefers-color-scheme`)
|
|
1835
|
+
* - Brand accent (`#6366F1` light / `#818CF8` dark) only on primary buttons & badges (WCAG AA)
|
|
1836
|
+
* - Monochromatic outlined SVG icons; no duplicate app logo header
|
|
1837
|
+
* - Auto-fitting inline card with no nested scrollbars (`notifyIntrinsicHeight`), max 2 bottom CTAs
|
|
1838
|
+
* - Inline 3–8 card carousel for `search_tools` catalog results
|
|
1839
|
+
* - Inline editable parameter controls for `plan_action` before clicking Execute
|
|
1840
|
+
* - Expand to fullscreen (`requestDisplayMode({ mode: "fullscreen" })`) for rich inspection
|
|
1841
|
+
*/
|
|
1842
|
+
export function renderMuxoUiWidgetHtml(baseUrl = "https://api.muxo.ai") {
|
|
1843
|
+
const safeBaseUrl = String(baseUrl || "https://api.muxo.ai").replace(/\/+$/, "");
|
|
1844
|
+
return `<!DOCTYPE html>
|
|
1845
|
+
<html lang="en">
|
|
1846
|
+
<head>
|
|
1847
|
+
<meta charset="utf-8" />
|
|
1848
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1849
|
+
<title>Muxo Action Card</title>
|
|
1850
|
+
<style>
|
|
1851
|
+
:root {
|
|
1852
|
+
color-scheme: light dark;
|
|
1853
|
+
--mx-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
1854
|
+
--mx-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
1855
|
+
--mx-bg: #ffffff;
|
|
1856
|
+
--mx-surface: #f7f7f8;
|
|
1857
|
+
--mx-border: rgba(13, 13, 13, 0.1);
|
|
1858
|
+
--mx-text: #0d0d0d;
|
|
1859
|
+
--mx-muted: #5d5d65;
|
|
1860
|
+
--mx-accent: #6366F1;
|
|
1861
|
+
--mx-accent-fg: #ffffff;
|
|
1862
|
+
--mx-badge-bg: rgba(99, 102, 241, 0.1);
|
|
1863
|
+
--mx-badge-fg: #4338ca;
|
|
1864
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.12);
|
|
1865
|
+
--mx-ok-fg: #047857;
|
|
1866
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.14);
|
|
1867
|
+
--mx-warn-fg: #b45309;
|
|
1868
|
+
--mx-err-bg: rgba(239, 68, 68, 0.12);
|
|
1869
|
+
--mx-err-fg: #b91c1c;
|
|
1870
|
+
}
|
|
1871
|
+
@media (prefers-color-scheme: dark) {
|
|
1872
|
+
:root:not([data-theme="light"]) {
|
|
1873
|
+
--mx-bg: #171717;
|
|
1874
|
+
--mx-surface: #212121;
|
|
1875
|
+
--mx-border: rgba(255, 255, 255, 0.12);
|
|
1876
|
+
--mx-text: #ececec;
|
|
1877
|
+
--mx-muted: #9b9ba4;
|
|
1878
|
+
--mx-accent: #818CF8;
|
|
1879
|
+
--mx-accent-fg: #0d0d0d;
|
|
1880
|
+
--mx-badge-bg: rgba(129, 140, 248, 0.16);
|
|
1881
|
+
--mx-badge-fg: #c7d2fe;
|
|
1882
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.18);
|
|
1883
|
+
--mx-ok-fg: #6ee7b7;
|
|
1884
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.2);
|
|
1885
|
+
--mx-warn-fg: #fcd34d;
|
|
1886
|
+
--mx-err-bg: rgba(239, 68, 68, 0.2);
|
|
1887
|
+
--mx-err-fg: #fca5a5;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
:root[data-theme="dark"] {
|
|
1891
|
+
--mx-bg: #171717;
|
|
1892
|
+
--mx-surface: #212121;
|
|
1893
|
+
--mx-border: rgba(255, 255, 255, 0.12);
|
|
1894
|
+
--mx-text: #ececec;
|
|
1895
|
+
--mx-muted: #9b9ba4;
|
|
1896
|
+
--mx-accent: #818CF8;
|
|
1897
|
+
--mx-accent-fg: #0d0d0d;
|
|
1898
|
+
--mx-badge-bg: rgba(129, 140, 248, 0.16);
|
|
1899
|
+
--mx-badge-fg: #c7d2fe;
|
|
1900
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.18);
|
|
1901
|
+
--mx-ok-fg: #6ee7b7;
|
|
1902
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.2);
|
|
1903
|
+
--mx-warn-fg: #fcd34d;
|
|
1904
|
+
--mx-err-bg: rgba(239, 68, 68, 0.2);
|
|
1905
|
+
--mx-err-fg: #fca5a5;
|
|
1906
|
+
}
|
|
1907
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1908
|
+
html, body {
|
|
1909
|
+
font-family: var(--mx-font);
|
|
1910
|
+
font-size: 14px;
|
|
1911
|
+
line-height: 1.45;
|
|
1912
|
+
color: var(--mx-text);
|
|
1913
|
+
background: transparent;
|
|
1914
|
+
-webkit-font-smoothing: antialiased;
|
|
1915
|
+
}
|
|
1916
|
+
body[data-mode="inline"] {
|
|
1917
|
+
overflow: hidden;
|
|
1918
|
+
}
|
|
1919
|
+
.mx-card {
|
|
1920
|
+
background: var(--mx-bg);
|
|
1921
|
+
border: 1px solid var(--mx-border);
|
|
1922
|
+
border-radius: 14px;
|
|
1923
|
+
padding: 16px;
|
|
1924
|
+
display: flex;
|
|
1925
|
+
flex-direction: column;
|
|
1926
|
+
gap: 12px;
|
|
1927
|
+
}
|
|
1928
|
+
.mx-header {
|
|
1929
|
+
display: flex;
|
|
1930
|
+
align-items: flex-start;
|
|
1931
|
+
justify-content: space-between;
|
|
1932
|
+
gap: 10px;
|
|
1933
|
+
}
|
|
1934
|
+
.mx-title-wrap {
|
|
1935
|
+
display: flex;
|
|
1936
|
+
align-items: center;
|
|
1937
|
+
gap: 8px;
|
|
1938
|
+
min-width: 0;
|
|
1939
|
+
}
|
|
1940
|
+
.mx-icon {
|
|
1941
|
+
width: 18px;
|
|
1942
|
+
height: 18px;
|
|
1943
|
+
flex-shrink: 0;
|
|
1944
|
+
color: var(--mx-muted);
|
|
1945
|
+
}
|
|
1946
|
+
.mx-title {
|
|
1947
|
+
font-size: 15px;
|
|
1948
|
+
font-weight: 600;
|
|
1949
|
+
white-space: nowrap;
|
|
1950
|
+
overflow: hidden;
|
|
1951
|
+
text-overflow: ellipsis;
|
|
1952
|
+
}
|
|
1953
|
+
.mx-subtitle {
|
|
1954
|
+
font-size: 12px;
|
|
1955
|
+
color: var(--mx-muted);
|
|
1956
|
+
margin-top: 2px;
|
|
1957
|
+
}
|
|
1958
|
+
.mx-header-right {
|
|
1959
|
+
display: flex;
|
|
1960
|
+
align-items: center;
|
|
1961
|
+
gap: 6px;
|
|
1962
|
+
flex-shrink: 0;
|
|
1963
|
+
}
|
|
1964
|
+
.mx-badge {
|
|
1965
|
+
display: inline-flex;
|
|
1966
|
+
align-items: center;
|
|
1967
|
+
gap: 4px;
|
|
1968
|
+
padding: 2px 8px;
|
|
1969
|
+
border-radius: 999px;
|
|
1970
|
+
font-size: 11px;
|
|
1971
|
+
font-weight: 600;
|
|
1972
|
+
letter-spacing: 0.01em;
|
|
1973
|
+
background: var(--mx-badge-bg);
|
|
1974
|
+
color: var(--mx-badge-fg);
|
|
1975
|
+
}
|
|
1976
|
+
.mx-badge.ok { background: var(--mx-ok-bg); color: var(--mx-ok-fg); }
|
|
1977
|
+
.mx-badge.warn { background: var(--mx-warn-bg); color: var(--mx-warn-fg); }
|
|
1978
|
+
.mx-badge.err { background: var(--mx-err-bg); color: var(--mx-err-fg); }
|
|
1979
|
+
.mx-expand-btn {
|
|
1980
|
+
background: transparent;
|
|
1981
|
+
border: 1px solid var(--mx-border);
|
|
1982
|
+
color: var(--mx-muted);
|
|
1983
|
+
border-radius: 8px;
|
|
1984
|
+
width: 26px;
|
|
1985
|
+
height: 26px;
|
|
1986
|
+
display: inline-flex;
|
|
1987
|
+
align-items: center;
|
|
1988
|
+
justify-content: center;
|
|
1989
|
+
cursor: pointer;
|
|
1990
|
+
}
|
|
1991
|
+
.mx-expand-btn:hover { color: var(--mx-text); background: var(--mx-surface); }
|
|
1992
|
+
.mx-grid {
|
|
1993
|
+
display: grid;
|
|
1994
|
+
grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
|
|
1995
|
+
gap: 8px;
|
|
1996
|
+
}
|
|
1997
|
+
.mx-stat {
|
|
1998
|
+
background: var(--mx-surface);
|
|
1999
|
+
border-radius: 10px;
|
|
2000
|
+
padding: 8px 10px;
|
|
2001
|
+
}
|
|
2002
|
+
.mx-stat-label {
|
|
2003
|
+
font-size: 11px;
|
|
2004
|
+
color: var(--mx-muted);
|
|
2005
|
+
}
|
|
2006
|
+
.mx-stat-value {
|
|
2007
|
+
font-size: 13px;
|
|
2008
|
+
font-weight: 600;
|
|
2009
|
+
margin-top: 2px;
|
|
2010
|
+
word-break: break-word;
|
|
2011
|
+
}
|
|
2012
|
+
.mx-mono { font-family: var(--mx-mono); font-size: 12px; }
|
|
2013
|
+
.mx-edit-row {
|
|
2014
|
+
display: flex;
|
|
2015
|
+
flex-direction: column;
|
|
2016
|
+
gap: 6px;
|
|
2017
|
+
background: var(--mx-surface);
|
|
2018
|
+
padding: 10px;
|
|
2019
|
+
border-radius: 10px;
|
|
2020
|
+
}
|
|
2021
|
+
.mx-edit-field {
|
|
2022
|
+
display: flex;
|
|
2023
|
+
align-items: center;
|
|
2024
|
+
justify-content: space-between;
|
|
2025
|
+
gap: 8px;
|
|
2026
|
+
}
|
|
2027
|
+
.mx-edit-field label {
|
|
2028
|
+
font-size: 12px;
|
|
2029
|
+
color: var(--mx-muted);
|
|
2030
|
+
min-width: 90px;
|
|
2031
|
+
}
|
|
2032
|
+
.mx-edit-field input {
|
|
2033
|
+
flex: 1;
|
|
2034
|
+
background: var(--mx-bg);
|
|
2035
|
+
color: var(--mx-text);
|
|
2036
|
+
border: 1px solid var(--mx-border);
|
|
2037
|
+
border-radius: 6px;
|
|
2038
|
+
padding: 4px 8px;
|
|
2039
|
+
font-family: var(--mx-font);
|
|
2040
|
+
font-size: 12px;
|
|
2041
|
+
}
|
|
2042
|
+
/* Inline Carousel (3-8 items per OpenAI UI Guidelines) */
|
|
2043
|
+
.mx-carousel {
|
|
2044
|
+
display: flex;
|
|
2045
|
+
gap: 10px;
|
|
2046
|
+
overflow-x: auto;
|
|
2047
|
+
scroll-snap-type: x mandatory;
|
|
2048
|
+
padding-bottom: 4px;
|
|
2049
|
+
scrollbar-width: none;
|
|
2050
|
+
}
|
|
2051
|
+
.mx-carousel::-webkit-scrollbar { display: none; }
|
|
2052
|
+
.mx-carousel-item {
|
|
2053
|
+
scroll-snap-align: start;
|
|
2054
|
+
flex: 0 0 210px;
|
|
2055
|
+
background: var(--mx-surface);
|
|
2056
|
+
border: 1px solid var(--mx-border);
|
|
2057
|
+
border-radius: 12px;
|
|
2058
|
+
padding: 12px;
|
|
2059
|
+
display: flex;
|
|
2060
|
+
flex-direction: column;
|
|
2061
|
+
justify-content: space-between;
|
|
2062
|
+
gap: 8px;
|
|
2063
|
+
}
|
|
2064
|
+
.mx-carousel-title {
|
|
2065
|
+
font-size: 13px;
|
|
2066
|
+
font-weight: 600;
|
|
2067
|
+
white-space: nowrap;
|
|
2068
|
+
overflow: hidden;
|
|
2069
|
+
text-overflow: ellipsis;
|
|
2070
|
+
}
|
|
2071
|
+
.mx-carousel-desc {
|
|
2072
|
+
font-size: 12px;
|
|
2073
|
+
color: var(--mx-muted);
|
|
2074
|
+
display: -webkit-box;
|
|
2075
|
+
-webkit-line-clamp: 2;
|
|
2076
|
+
-webkit-box-orient: vertical;
|
|
2077
|
+
overflow: hidden;
|
|
2078
|
+
}
|
|
2079
|
+
/* Max 2 bottom actions per Inline Card */
|
|
2080
|
+
.mx-actions {
|
|
2081
|
+
display: flex;
|
|
2082
|
+
align-items: center;
|
|
2083
|
+
justify-content: flex-end;
|
|
2084
|
+
gap: 8px;
|
|
2085
|
+
padding-top: 4px;
|
|
2086
|
+
}
|
|
2087
|
+
.mx-btn {
|
|
2088
|
+
appearance: none;
|
|
2089
|
+
border-radius: 8px;
|
|
2090
|
+
padding: 7px 12px;
|
|
2091
|
+
font-family: var(--mx-font);
|
|
2092
|
+
font-size: 13px;
|
|
2093
|
+
font-weight: 600;
|
|
2094
|
+
cursor: pointer;
|
|
2095
|
+
border: 1px solid transparent;
|
|
2096
|
+
display: inline-flex;
|
|
2097
|
+
align-items: center;
|
|
2098
|
+
justify-content: center;
|
|
2099
|
+
gap: 6px;
|
|
2100
|
+
transition: opacity 0.15s ease;
|
|
2101
|
+
}
|
|
2102
|
+
.mx-btn:disabled { opacity: 0.55; cursor: default; }
|
|
2103
|
+
.mx-btn-primary {
|
|
2104
|
+
background: var(--mx-accent);
|
|
2105
|
+
color: var(--mx-accent-fg);
|
|
2106
|
+
}
|
|
2107
|
+
.mx-btn-secondary {
|
|
2108
|
+
background: transparent;
|
|
2109
|
+
border-color: var(--mx-border);
|
|
2110
|
+
color: var(--mx-text);
|
|
2111
|
+
}
|
|
2112
|
+
.mx-fullscreen-details {
|
|
2113
|
+
display: none;
|
|
2114
|
+
background: var(--mx-surface);
|
|
2115
|
+
border-radius: 10px;
|
|
2116
|
+
padding: 10px;
|
|
2117
|
+
font-family: var(--mx-mono);
|
|
2118
|
+
font-size: 11px;
|
|
2119
|
+
white-space: pre-wrap;
|
|
2120
|
+
word-break: break-all;
|
|
2121
|
+
}
|
|
2122
|
+
body[data-mode="fullscreen"] .mx-fullscreen-details {
|
|
2123
|
+
display: block;
|
|
2124
|
+
}
|
|
2125
|
+
</style>
|
|
2126
|
+
</head>
|
|
2127
|
+
<body data-mode="inline">
|
|
2128
|
+
<div id="mx-root" class="mx-card" role="region" aria-label="Muxo Action Widget"></div>
|
|
2129
|
+
<script>
|
|
2130
|
+
(function () {
|
|
2131
|
+
const BASE_URL = ${JSON.stringify(safeBaseUrl)};
|
|
2132
|
+
const root = document.getElementById("mx-root");
|
|
2133
|
+
const pendingRequests = new Map();
|
|
2134
|
+
let nextRequestId = 1;
|
|
2135
|
+
let currentData = null;
|
|
2136
|
+
let latestToolInput = null;
|
|
2137
|
+
let editedInputs = {};
|
|
2138
|
+
|
|
2139
|
+
function mcpRpcRequest(method, params) {
|
|
2140
|
+
if (!window.parent || window.parent === window) {
|
|
2141
|
+
return Promise.resolve(null);
|
|
2142
|
+
}
|
|
2143
|
+
const id = nextRequestId++;
|
|
2144
|
+
window.parent.postMessage({ jsonrpc: "2.0", id, method: method, params: params || {} }, "*");
|
|
2145
|
+
return new Promise(function (resolve, reject) {
|
|
2146
|
+
pendingRequests.set(id, { resolve: resolve, reject: reject });
|
|
2147
|
+
setTimeout(function () {
|
|
2148
|
+
if (pendingRequests.has(id)) {
|
|
2149
|
+
pendingRequests.delete(id);
|
|
2150
|
+
resolve(null);
|
|
2151
|
+
}
|
|
2152
|
+
}, 15000);
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function persistWidgetState(modelContent, privateContent) {
|
|
2157
|
+
const snapshot = {
|
|
2158
|
+
modelContent: modelContent,
|
|
2159
|
+
privateContent: privateContent || {},
|
|
2160
|
+
};
|
|
2161
|
+
if (window.openai && typeof window.openai.setWidgetState === "function") {
|
|
2162
|
+
window.openai.setWidgetState(snapshot);
|
|
2163
|
+
}
|
|
2164
|
+
mcpRpcRequest("ui/update-model-context", { context: modelContent });
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
function syncThemeAndMode() {
|
|
2168
|
+
const oa = window.openai || {};
|
|
2169
|
+
if (oa.theme === "dark" || oa.theme === "light") {
|
|
2170
|
+
document.documentElement.setAttribute("data-theme", oa.theme);
|
|
2171
|
+
}
|
|
2172
|
+
if (oa.locale && typeof oa.locale === "string") {
|
|
2173
|
+
document.documentElement.lang = oa.locale;
|
|
2174
|
+
}
|
|
2175
|
+
const mode = oa.displayMode === "fullscreen" ? "fullscreen" : "inline";
|
|
2176
|
+
document.body.setAttribute("data-mode", mode);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function notifyHeight() {
|
|
2180
|
+
if (window.openai && typeof window.openai.notifyIntrinsicHeight === "function") {
|
|
2181
|
+
window.openai.notifyIntrinsicHeight(document.body.scrollHeight);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function esc(val) {
|
|
2186
|
+
return String(val == null ? "" : val)
|
|
2187
|
+
.replace(/&/g, "&")
|
|
2188
|
+
.replace(/</g, "<")
|
|
2189
|
+
.replace(/>/g, ">")
|
|
2190
|
+
.replace(/"/g, """);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function resolveUrl(href) {
|
|
2194
|
+
if (!href) return "";
|
|
2195
|
+
return href.startsWith("http") ? href : BASE_URL + (href.startsWith("/") ? href : "/" + href);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
function openUrl(href) {
|
|
2199
|
+
const target = resolveUrl(href);
|
|
2200
|
+
if (!target) return;
|
|
2201
|
+
if (window.openai && typeof window.openai.openExternal === "function") {
|
|
2202
|
+
window.openai.openExternal({ href: target });
|
|
2203
|
+
} else {
|
|
2204
|
+
window.open(target, "_blank", "noopener,noreferrer");
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function setOpenInAppTarget(href) {
|
|
2209
|
+
const target = resolveUrl(href);
|
|
2210
|
+
if (target && window.openai && typeof window.openai.setOpenInAppUrl === "function") {
|
|
2211
|
+
window.openai.setOpenInAppUrl({ href: target });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
async function invokeTool(name, args, btn) {
|
|
2216
|
+
if (btn) btn.disabled = true;
|
|
2217
|
+
try {
|
|
2218
|
+
if (window.openai && typeof window.openai.callTool === "function") {
|
|
2219
|
+
const res = await window.openai.callTool(name, args || {});
|
|
2220
|
+
if (res && res.structuredContent) {
|
|
2221
|
+
render(res.structuredContent);
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const rpcRes = await mcpRpcRequest("tools/call", {
|
|
2226
|
+
name: name,
|
|
2227
|
+
arguments: args || {},
|
|
2228
|
+
});
|
|
2229
|
+
if (rpcRes && rpcRes.structuredContent) {
|
|
2230
|
+
render(rpcRes.structuredContent);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (window.openai && typeof window.openai.sendFollowUpMessage === "function") {
|
|
2234
|
+
await window.openai.sendFollowUpMessage({
|
|
2235
|
+
prompt: "Please call " + name + " with " + JSON.stringify(args || {})
|
|
2236
|
+
});
|
|
2237
|
+
} else {
|
|
2238
|
+
await mcpRpcRequest("ui/message", {
|
|
2239
|
+
role: "user",
|
|
2240
|
+
content: { type: "text", text: "Please call " + name + " with " + JSON.stringify(args || {}) },
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
} finally {
|
|
2244
|
+
if (btn) btn.disabled = false;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function toggleFullscreen() {
|
|
2249
|
+
const next = document.body.getAttribute("data-mode") === "fullscreen" ? "inline" : "fullscreen";
|
|
2250
|
+
document.body.setAttribute("data-mode", next);
|
|
2251
|
+
if (window.openai && typeof window.openai.requestDisplayMode === "function") {
|
|
2252
|
+
window.openai.requestDisplayMode({ mode: next });
|
|
2253
|
+
}
|
|
2254
|
+
notifyHeight();
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function render(data) {
|
|
2258
|
+
syncThemeAndMode();
|
|
2259
|
+
const oa = window.openai || {};
|
|
2260
|
+
const metaResult =
|
|
2261
|
+
oa.toolResponseMetadata &&
|
|
2262
|
+
oa.toolResponseMetadata.mcp_tool_result &&
|
|
2263
|
+
oa.toolResponseMetadata.mcp_tool_result.structuredContent;
|
|
2264
|
+
currentData = data || oa.toolOutput || metaResult || {};
|
|
2265
|
+
|
|
2266
|
+
// 1. Inline Carousel for search_tools (3-8 cards per OpenAI UI Guidelines)
|
|
2267
|
+
if (Array.isArray(currentData.tools) && currentData.tools.length > 0) {
|
|
2268
|
+
const items = currentData.tools.slice(0, 8);
|
|
2269
|
+
root.innerHTML =
|
|
2270
|
+
'<div class="mx-header">' +
|
|
2271
|
+
'<div class="mx-title-wrap">' +
|
|
2272
|
+
'<svg class="mx-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>' +
|
|
2273
|
+
'<div><div class="mx-title">Unified Tool Catalog</div>' +
|
|
2274
|
+
'<div class="mx-subtitle">' + esc(currentData.count || items.length) + ' governed capabilities available</div></div>' +
|
|
2275
|
+
'</div>' +
|
|
2276
|
+
'<span class="mx-badge">' + esc(items.length) + ' shown</span>' +
|
|
2277
|
+
'</div>' +
|
|
2278
|
+
'<div class="mx-carousel">' +
|
|
2279
|
+
items.map(function (t) {
|
|
2280
|
+
const badgeClass = t.riskLevel === "read" ? "ok" : (t.riskLevel === "destructive" || t.riskLevel === "financial" ? "warn" : "");
|
|
2281
|
+
return (
|
|
2282
|
+
'<div class="mx-carousel-item">' +
|
|
2283
|
+
'<div>' +
|
|
2284
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;gap:6px;margin-bottom:4px;">' +
|
|
2285
|
+
'<span class="mx-badge ' + badgeClass + '">' + esc(t.riskLevel || "read") + '</span>' +
|
|
2286
|
+
'<span class="mx-subtitle mx-mono">' + esc(t.creditCost || 1) + ' cr</span>' +
|
|
2287
|
+
'</div>' +
|
|
2288
|
+
'<div class="mx-carousel-title" title="' + esc(t.name || t.slug) + '">' + esc(t.name || t.slug) + '</div>' +
|
|
2289
|
+
'<div class="mx-subtitle mx-mono" style="margin-bottom:4px;">' + esc(t.slug) + '</div>' +
|
|
2290
|
+
'<div class="mx-carousel-desc">' + esc(t.description || "") + '</div>' +
|
|
2291
|
+
'</div>' +
|
|
2292
|
+
'<button class="mx-btn mx-btn-secondary" data-plan-slug="' + esc(t.slug) + '" style="width:100%;padding:5px 8px;font-size:12px;">Plan Action</button>' +
|
|
2293
|
+
'</div>'
|
|
2294
|
+
);
|
|
2295
|
+
}).join("") +
|
|
2296
|
+
'</div>';
|
|
2297
|
+
root.querySelectorAll("[data-plan-slug]").forEach(function (btn) {
|
|
2298
|
+
btn.addEventListener("click", function () {
|
|
2299
|
+
const slug = btn.getAttribute("data-plan-slug");
|
|
2300
|
+
persistWidgetState("Selected tool " + slug + " for planning.", { selectedToolSlug: slug });
|
|
2301
|
+
invokeTool("plan_action", { tool_slug: slug, input: {} }, btn);
|
|
2302
|
+
});
|
|
2303
|
+
});
|
|
2304
|
+
notifyHeight();
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// 2. Determine Inline Card state (Plan, Handoff, Execution Run, Receipt, or Workspace Summary)
|
|
2309
|
+
const plan = currentData.plan || (currentData.id && String(currentData.id).startsWith("plan_") ? currentData : null);
|
|
2310
|
+
const run = currentData.run || (currentData.id && String(currentData.id).startsWith("run_") ? currentData : null);
|
|
2311
|
+
const receipt = currentData.receipt || (currentData.id && String(currentData.id).startsWith("rcpt_") ? currentData : null);
|
|
2312
|
+
const handoff = currentData.humanHandoff || (plan && plan.humanHandoff) || null;
|
|
2313
|
+
const authHandoff = currentData.authHandoff || (plan && plan.authHandoff) || null;
|
|
2314
|
+
const handoffUrl =
|
|
2315
|
+
(handoff && (handoff.url || handoff.actionUrl || handoff.oauthRedirectUrl || handoff.approvalUrl)) ||
|
|
2316
|
+
(authHandoff && authHandoff.redirectUrl) ||
|
|
2317
|
+
currentData.redirectUrl ||
|
|
2318
|
+
null;
|
|
2319
|
+
const isHandoffActive = Boolean(
|
|
2320
|
+
(handoff && (handoff.required !== false && (handoffUrl || handoff.kind))) ||
|
|
2321
|
+
(authHandoff && authHandoff.required)
|
|
2322
|
+
);
|
|
2323
|
+
|
|
2324
|
+
let title = "Muxo Governed Operation";
|
|
2325
|
+
let subtitle = "Cryptographically audited agent execution";
|
|
2326
|
+
let statusLabel = "Ready";
|
|
2327
|
+
let statusTone = "ok";
|
|
2328
|
+
let stats = [];
|
|
2329
|
+
let editFieldsHtml = "";
|
|
2330
|
+
let primaryBtn = null;
|
|
2331
|
+
let secondaryBtn = null;
|
|
2332
|
+
|
|
2333
|
+
if (isHandoffActive && !run) {
|
|
2334
|
+
const isOauth =
|
|
2335
|
+
(handoff && (handoff.kind === "oauth" || handoff.kind === "oauth_connection")) ||
|
|
2336
|
+
Boolean(authHandoff && authHandoff.required);
|
|
2337
|
+
title =
|
|
2338
|
+
(handoff && (handoff.headline || handoff.title)) ||
|
|
2339
|
+
(isOauth ? "OAuth Connection Required" : "Human Approval Required");
|
|
2340
|
+
subtitle =
|
|
2341
|
+
(handoff && (handoff.instructions || handoff.summary)) ||
|
|
2342
|
+
"Complete this step in your browser to resume governed execution";
|
|
2343
|
+
statusLabel = isOauth ? "OAuth Handoff" : "Approval Needed";
|
|
2344
|
+
statusTone = "warn";
|
|
2345
|
+
const prov =
|
|
2346
|
+
(handoff && handoff.provider) ||
|
|
2347
|
+
(authHandoff && authHandoff.providerSlug) ||
|
|
2348
|
+
currentData.provider ||
|
|
2349
|
+
currentData.providerSlug ||
|
|
2350
|
+
(plan && plan.providerSlug);
|
|
2351
|
+
stats.push({
|
|
2352
|
+
label: "Provider / Tool",
|
|
2353
|
+
value: (plan && plan.toolSlug) || prov || "External Service"
|
|
2354
|
+
});
|
|
2355
|
+
if (plan && plan.id) stats.push({ label: "Plan ID", value: plan.id, mono: true });
|
|
2356
|
+
if (handoffUrl) {
|
|
2357
|
+
setOpenInAppTarget(handoffUrl);
|
|
2358
|
+
primaryBtn = {
|
|
2359
|
+
label: (handoff && handoff.ctaLabel) || (isOauth ? "Connect Account" : "Review & Approve"),
|
|
2360
|
+
onClick: function () { openUrl(handoffUrl); }
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
if (isOauth && prov) {
|
|
2364
|
+
secondaryBtn = {
|
|
2365
|
+
label: "Verify Connection",
|
|
2366
|
+
onClick: function (btn) { invokeTool("wait_for_connection", { provider: prov, timeout_seconds: 15 }, btn); }
|
|
2367
|
+
};
|
|
2368
|
+
} else if (plan && plan.id) {
|
|
2369
|
+
secondaryBtn = {
|
|
2370
|
+
label: "Check Status",
|
|
2371
|
+
onClick: function (btn) { invokeTool("get_plan", { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 10 }, btn); }
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
} else if (plan && !run) {
|
|
2375
|
+
title = plan.toolName || plan.toolSlug || "Governed Action Plan";
|
|
2376
|
+
subtitle = plan.policyReason || "Evaluated against workspace guardrails";
|
|
2377
|
+
const approved = plan.approvalStatus === "approved" || plan.approvalStatus === "auto_approved";
|
|
2378
|
+
statusLabel = approved ? "Approved (" + (plan.riskLevel || "read") + ")" : String(plan.approvalStatus || "planned");
|
|
2379
|
+
statusTone = approved ? "ok" : "warn";
|
|
2380
|
+
stats.push({ label: "Plan ID", value: plan.id, mono: true });
|
|
2381
|
+
stats.push({ label: "Lane", value: plan.lane || "composio_instant" });
|
|
2382
|
+
stats.push({ label: "Estimated Cost", value: String(plan.estimatedCreditCost || 1) + " credits" });
|
|
2383
|
+
|
|
2384
|
+
const inputObj =
|
|
2385
|
+
(plan.input && typeof plan.input === "object")
|
|
2386
|
+
? plan.input
|
|
2387
|
+
: (latestToolInput && latestToolInput.input && typeof latestToolInput.input === "object")
|
|
2388
|
+
? latestToolInput.input
|
|
2389
|
+
: {};
|
|
2390
|
+
const keys = Object.keys(inputObj).slice(0, 3);
|
|
2391
|
+
editedInputs = {};
|
|
2392
|
+
if (keys.length > 0 && approved) {
|
|
2393
|
+
editFieldsHtml = '<div class="mx-edit-row">' +
|
|
2394
|
+
keys.map(function (k) {
|
|
2395
|
+
const val = typeof inputObj[k] === "object" ? JSON.stringify(inputObj[k]) : String(inputObj[k] ?? "");
|
|
2396
|
+
return '<div class="mx-edit-field"><label>' + esc(k) + '</label><input data-input-key="' + esc(k) + '" value="' + esc(val) + '" /></div>';
|
|
2397
|
+
}).join("") +
|
|
2398
|
+
'</div>';
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
if (approved) {
|
|
2402
|
+
const execTool = "execute_" + String(plan.riskLevel || "read");
|
|
2403
|
+
primaryBtn = {
|
|
2404
|
+
label: "Execute Plan",
|
|
2405
|
+
onClick: function (btn) {
|
|
2406
|
+
if (Object.keys(editedInputs).length > 0 && plan.toolSlug) {
|
|
2407
|
+
const merged = Object.assign({}, inputObj, editedInputs);
|
|
2408
|
+
invokeTool("plan_action", { tool_slug: plan.toolSlug, input: merged, reason: plan.reason || "Updated parameters in widget" }, btn);
|
|
2409
|
+
} else {
|
|
2410
|
+
invokeTool(execTool, { plan_id: plan.id }, btn);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
} else if (plan.approvalUrl) {
|
|
2415
|
+
setOpenInAppTarget(plan.approvalUrl);
|
|
2416
|
+
primaryBtn = {
|
|
2417
|
+
label: "Approve Plan",
|
|
2418
|
+
onClick: function () { openUrl(plan.approvalUrl); }
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
secondaryBtn = {
|
|
2422
|
+
label: "Refresh Plan",
|
|
2423
|
+
onClick: function (btn) { invokeTool("get_plan", { plan_id: plan.id }, btn); }
|
|
2424
|
+
};
|
|
2425
|
+
} else if (run || receipt) {
|
|
2426
|
+
const r = run || {};
|
|
2427
|
+
const rc = receipt || {};
|
|
2428
|
+
title = r.toolSlug || rc.toolSlug || "Execution Receipt";
|
|
2429
|
+
subtitle = (rc.agenticReceipt && rc.agenticReceipt.publicKeyId)
|
|
2430
|
+
? "Signed Ed25519 (" + rc.agenticReceipt.publicKeyId + ") + SHA-256 Chain"
|
|
2431
|
+
: "Cryptographically verified execution record";
|
|
2432
|
+
const ok = (r.status || rc.status || "succeeded") === "succeeded";
|
|
2433
|
+
statusLabel = ok ? "Verified" : String(r.status || rc.status || "completed");
|
|
2434
|
+
statusTone = ok ? "ok" : "err";
|
|
2435
|
+
if (r.id || rc.runId) stats.push({ label: "Run ID", value: r.id || rc.runId, mono: true });
|
|
2436
|
+
if (rc.id) stats.push({ label: "Receipt ID", value: rc.id, mono: true });
|
|
2437
|
+
if (rc.outputHash) stats.push({ label: "Output SHA-256", value: String(rc.outputHash).slice(0, 16) + "…", mono: true });
|
|
2438
|
+
|
|
2439
|
+
const out = r.output || {};
|
|
2440
|
+
const liveUrl = out.liveUrl || out.deploymentUrl || (out.result && (out.result.liveUrl || out.result.url));
|
|
2441
|
+
if (liveUrl) {
|
|
2442
|
+
setOpenInAppTarget(liveUrl);
|
|
2443
|
+
primaryBtn = { label: "Open Live URL", onClick: function () { openUrl(liveUrl); } };
|
|
2444
|
+
} else if (r.id || rc.id) {
|
|
2445
|
+
primaryBtn = {
|
|
2446
|
+
label: "Verify Receipt",
|
|
2447
|
+
onClick: function (btn) { invokeTool("get_receipt", { receipt_id: rc.id || r.id, verify_chain: true }, btn); }
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
} else {
|
|
2451
|
+
title = (currentData.workspace && currentData.workspace.name) || "Muxo Workspace";
|
|
2452
|
+
subtitle = "Bi-temporal memory, policy guardrails & execution lanes active";
|
|
2453
|
+
statusLabel = "Active";
|
|
2454
|
+
statusTone = "ok";
|
|
2455
|
+
if (currentData.workspace && currentData.workspace.id) {
|
|
2456
|
+
stats.push({ label: "Workspace", value: currentData.workspace.id, mono: true });
|
|
2457
|
+
}
|
|
2458
|
+
if (currentData.billing && currentData.billing.creditsRemaining != null) {
|
|
2459
|
+
stats.push({ label: "Credits", value: String(currentData.billing.creditsRemaining) });
|
|
2460
|
+
}
|
|
2461
|
+
primaryBtn = {
|
|
2462
|
+
label: "Browse Tool Catalog",
|
|
2463
|
+
onClick: function (btn) { invokeTool("search_tools", { query: "" }, btn); }
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
const expandIcon =
|
|
2468
|
+
'<button class="mx-expand-btn" id="mx-expand" title="Toggle Fullscreen Inspector" aria-label="Toggle Fullscreen Inspector">' +
|
|
2469
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>' +
|
|
2470
|
+
'</button>';
|
|
2471
|
+
|
|
2472
|
+
root.innerHTML =
|
|
2473
|
+
'<div class="mx-header">' +
|
|
2474
|
+
'<div class="mx-title-wrap">' +
|
|
2475
|
+
'<svg class="mx-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M12 2L3 7v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-9-5z"/></svg>' +
|
|
2476
|
+
'<div><div class="mx-title">' + esc(title) + '</div>' +
|
|
2477
|
+
'<div class="mx-subtitle">' + esc(subtitle) + '</div></div>' +
|
|
2478
|
+
'</div>' +
|
|
2479
|
+
'<div class="mx-header-right">' +
|
|
2480
|
+
'<span class="mx-badge ' + statusTone + '">' + esc(statusLabel) + '</span>' +
|
|
2481
|
+
expandIcon +
|
|
2482
|
+
'</div>' +
|
|
2483
|
+
'</div>' +
|
|
2484
|
+
(stats.length > 0
|
|
2485
|
+
? '<div class="mx-grid">' +
|
|
2486
|
+
stats.map(function (s) {
|
|
2487
|
+
return '<div class="mx-stat"><div class="mx-stat-label">' + esc(s.label) + '</div>' +
|
|
2488
|
+
'<div class="mx-stat-value ' + (s.mono ? 'mx-mono' : '') + '">' + esc(s.value) + '</div></div>';
|
|
2489
|
+
}).join("") +
|
|
2490
|
+
'</div>'
|
|
2491
|
+
: '') +
|
|
2492
|
+
editFieldsHtml +
|
|
2493
|
+
'<div class="mx-fullscreen-details">' + esc(JSON.stringify(currentData, null, 2)) + '</div>' +
|
|
2494
|
+
((primaryBtn || secondaryBtn)
|
|
2495
|
+
? '<div class="mx-actions">' +
|
|
2496
|
+
(secondaryBtn ? '<button class="mx-btn mx-btn-secondary" id="mx-btn-sec">' + esc(secondaryBtn.label) + '</button>' : '') +
|
|
2497
|
+
(primaryBtn ? '<button class="mx-btn mx-btn-primary" id="mx-btn-pri">' + esc(primaryBtn.label) + '</button>' : '') +
|
|
2498
|
+
'</div>'
|
|
2499
|
+
: '');
|
|
2500
|
+
|
|
2501
|
+
const expBtn = document.getElementById("mx-expand");
|
|
2502
|
+
if (expBtn) expBtn.addEventListener("click", toggleFullscreen);
|
|
2503
|
+
const priEl = document.getElementById("mx-btn-pri");
|
|
2504
|
+
if (priEl && primaryBtn) priEl.addEventListener("click", function () { primaryBtn.onClick(priEl); });
|
|
2505
|
+
const secEl = document.getElementById("mx-btn-sec");
|
|
2506
|
+
if (secEl && secondaryBtn) secEl.addEventListener("click", function () { secondaryBtn.onClick(secEl); });
|
|
2507
|
+
|
|
2508
|
+
root.querySelectorAll("[data-input-key]").forEach(function (inp) {
|
|
2509
|
+
inp.addEventListener("change", function () {
|
|
2510
|
+
const key = inp.getAttribute("data-input-key");
|
|
2511
|
+
if (!key) return;
|
|
2512
|
+
editedInputs[key] = inp.value;
|
|
2513
|
+
if (priEl) priEl.textContent = "Re-Plan & Execute";
|
|
2514
|
+
persistWidgetState(
|
|
2515
|
+
"User edited plan input parameter " + key + " = " + inp.value,
|
|
2516
|
+
{ planId: plan && plan.id, editedInputs: editedInputs }
|
|
2517
|
+
);
|
|
2518
|
+
});
|
|
2519
|
+
});
|
|
2520
|
+
|
|
2521
|
+
notifyHeight();
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
window.addEventListener("openai:set_globals", function () { render(currentData); });
|
|
2525
|
+
window.addEventListener("message", function (ev) {
|
|
2526
|
+
if (ev.source && window.parent && ev.source !== window.parent) return;
|
|
2527
|
+
const msg = ev && ev.data;
|
|
2528
|
+
if (!msg || msg.jsonrpc !== "2.0") return;
|
|
2529
|
+
if (msg.id !== undefined && pendingRequests.has(msg.id)) {
|
|
2530
|
+
const pending = pendingRequests.get(msg.id);
|
|
2531
|
+
pendingRequests.delete(msg.id);
|
|
2532
|
+
if (msg.error) pending.reject(msg.error);
|
|
2533
|
+
else pending.resolve(msg.result);
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
if (msg.method === "ui/notifications/tool-input") {
|
|
2537
|
+
latestToolInput = msg.params || null;
|
|
2538
|
+
if (currentData) render(currentData);
|
|
2539
|
+
}
|
|
2540
|
+
if (msg.method === "ui/notifications/tool-result" && msg.params) {
|
|
2541
|
+
render(msg.params.structuredContent || msg.params);
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
|
|
2545
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2546
|
+
new ResizeObserver(notifyHeight).observe(document.body);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
mcpRpcRequest("ui/initialize", {
|
|
2550
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
2551
|
+
});
|
|
2552
|
+
|
|
2553
|
+
latestToolInput = (window.openai && window.openai.toolInput) || null;
|
|
2554
|
+
render((window.openai && window.openai.toolOutput) || null);
|
|
2555
|
+
})();
|
|
2556
|
+
</script>
|
|
2557
|
+
</body>
|
|
2558
|
+
</html>`;
|
|
2559
|
+
}
|