@muxoai/platform 1.4.1 → 1.4.3
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 +94 -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/durable-workflow.d.ts +71 -4
- package/dist/durable-workflow.js +199 -5
- package/dist/engine.d.ts +82 -3
- package/dist/engine.js +406 -41
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp-schema.d.ts +162 -1
- package/dist/mcp-schema.js +1544 -40
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +474 -26
- package/dist/memory-graph.d.ts +26 -2
- package/dist/memory-graph.js +127 -7
- package/dist/oss-suite.d.ts +217 -0
- package/dist/oss-suite.js +836 -0
- 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/sdk.d.ts +24 -1
- package/dist/sdk.js +53 -2
- package/dist/server.js +659 -64
- package/dist/store.js +2 -0
- package/dist/types.d.ts +34 -1
- 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,37 @@ export const MUXO_MCP_TOOLS = [
|
|
|
537
1055
|
"forget",
|
|
538
1056
|
"memory_graph",
|
|
539
1057
|
"set_guardrail",
|
|
1058
|
+
"reset_guardrails",
|
|
1059
|
+
"check_policy",
|
|
1060
|
+
"cedar_policy",
|
|
1061
|
+
"otel_traces",
|
|
1062
|
+
"roi_analytics",
|
|
1063
|
+
"playbooks",
|
|
1064
|
+
"run_playbook",
|
|
1065
|
+
"kill_switch",
|
|
540
1066
|
"governance_status",
|
|
541
1067
|
"scan_context",
|
|
542
1068
|
"list_workspaces",
|
|
543
1069
|
"switch_workspace",
|
|
544
1070
|
"onboard_team",
|
|
545
1071
|
],
|
|
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'",
|
|
1072
|
+
description: "Control-plane operation: 'inspect' (default), 'remember' (save fact/preference), 'recall' (list memories), 'forget' (delete matching memories), 'memory_graph' (Graphiti bi-temporal entity/relationship lineage with optional as_of time-travel & focal_entity 2-hop BFS), '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), 'cedar_policy' (compile/evaluate AWS Cedar & OPA Rego policy-as-code), 'otel_traces' (export W3C OpenTelemetry GenAI/MCP spans & Langfuse batch), 'roi_analytics' (FinOps & BYOK unit-economics ROI report), 'playbooks' / 'run_playbook' (list or execute turnkey multi-step Temporal Saga playbooks), '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
1073
|
},
|
|
548
1074
|
prompt: {
|
|
549
1075
|
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')",
|
|
1076
|
+
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')",
|
|
1077
|
+
},
|
|
1078
|
+
tool_slug: {
|
|
1079
|
+
type: "string",
|
|
1080
|
+
description: "Tool slug to dry-run evaluate when action='check_policy' (e.g. 'app.gmail.send_email', 'billing.stripe.create_refund')",
|
|
1081
|
+
},
|
|
1082
|
+
input: {
|
|
1083
|
+
type: "object",
|
|
1084
|
+
description: "Tool arguments object to dry-run evaluate against parameter guardrails and learned defaults when action='check_policy'",
|
|
1085
|
+
},
|
|
1086
|
+
enabled: {
|
|
1087
|
+
type: "boolean",
|
|
1088
|
+
description: "When action='kill_switch', set true to pause all workspace agent execution or false to resume execution",
|
|
551
1089
|
},
|
|
552
1090
|
parameter_rules: {
|
|
553
1091
|
type: "array",
|
|
@@ -628,6 +1166,22 @@ export const MUXO_MCP_TOOLS = [
|
|
|
628
1166
|
type: "boolean",
|
|
629
1167
|
description: "If true, attach the Bi-Temporal Knowledge Graph (nodes, activeEdges, historicalEdges, mermaid) to the context response",
|
|
630
1168
|
},
|
|
1169
|
+
as_of: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
description: "Optional ISO-8601 timestamp for Graphiti point-in-time ('as-of') bi-temporal knowledge graph reconstruction",
|
|
1172
|
+
},
|
|
1173
|
+
focal_entity: {
|
|
1174
|
+
type: "string",
|
|
1175
|
+
description: "Optional entity ID or label for k-hop BFS neighborhood subgraph extraction",
|
|
1176
|
+
},
|
|
1177
|
+
max_hops: {
|
|
1178
|
+
type: "number",
|
|
1179
|
+
description: "Maximum BFS traversal hops around focal_entity (1 to 4, default 2)",
|
|
1180
|
+
},
|
|
1181
|
+
slug: {
|
|
1182
|
+
type: "string",
|
|
1183
|
+
description: "Optional playbook slug when action='run_playbook' (e.g. 'fullstack-cloud-bootstrap', 'competitor-seo-growth-brief', 'incident-containment-audit', 'customer-release-readiness')",
|
|
1184
|
+
},
|
|
631
1185
|
plan_id: {
|
|
632
1186
|
type: "string",
|
|
633
1187
|
description: "Optional plan ID to inspect context for",
|
|
@@ -644,6 +1198,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
644
1198
|
destructiveHint: false,
|
|
645
1199
|
openWorldHint: false,
|
|
646
1200
|
},
|
|
1201
|
+
_meta: mcpToolUiMeta("Loading workspace context, memory & guardrails…", "Loaded workspace context & guardrails"),
|
|
647
1202
|
},
|
|
648
1203
|
{
|
|
649
1204
|
name: "execute_read",
|
|
@@ -662,6 +1217,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
662
1217
|
destructiveHint: false,
|
|
663
1218
|
openWorldHint: true,
|
|
664
1219
|
},
|
|
1220
|
+
_meta: mcpToolUiMeta("Executing governed read operation…", "Completed read operation"),
|
|
665
1221
|
},
|
|
666
1222
|
{
|
|
667
1223
|
name: "execute_write",
|
|
@@ -680,6 +1236,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
680
1236
|
destructiveHint: false,
|
|
681
1237
|
openWorldHint: true,
|
|
682
1238
|
},
|
|
1239
|
+
_meta: mcpToolUiMeta("Executing governed write operation…", "Completed write operation"),
|
|
683
1240
|
},
|
|
684
1241
|
{
|
|
685
1242
|
name: "execute_admin",
|
|
@@ -698,6 +1255,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
698
1255
|
destructiveHint: false,
|
|
699
1256
|
openWorldHint: true,
|
|
700
1257
|
},
|
|
1258
|
+
_meta: mcpToolUiMeta("Executing governed admin operation…", "Completed admin operation"),
|
|
701
1259
|
},
|
|
702
1260
|
{
|
|
703
1261
|
name: "execute_financial",
|
|
@@ -716,6 +1274,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
716
1274
|
destructiveHint: false,
|
|
717
1275
|
openWorldHint: true,
|
|
718
1276
|
},
|
|
1277
|
+
_meta: mcpToolUiMeta("Executing governed financial operation…", "Completed financial operation"),
|
|
719
1278
|
},
|
|
720
1279
|
{
|
|
721
1280
|
name: "execute_provisioning",
|
|
@@ -734,6 +1293,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
734
1293
|
destructiveHint: false,
|
|
735
1294
|
openWorldHint: true,
|
|
736
1295
|
},
|
|
1296
|
+
_meta: mcpToolUiMeta("Provisioning cloud infrastructure via Stripe Projects…", "Provisioned cloud resource"),
|
|
737
1297
|
},
|
|
738
1298
|
{
|
|
739
1299
|
name: "execute_destructive",
|
|
@@ -752,6 +1312,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
752
1312
|
destructiveHint: true,
|
|
753
1313
|
openWorldHint: true,
|
|
754
1314
|
},
|
|
1315
|
+
_meta: mcpToolUiMeta("Executing approved destructive operation…", "Completed destructive operation"),
|
|
755
1316
|
},
|
|
756
1317
|
{
|
|
757
1318
|
name: "get_run",
|
|
@@ -770,6 +1331,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
770
1331
|
destructiveHint: false,
|
|
771
1332
|
openWorldHint: false,
|
|
772
1333
|
},
|
|
1334
|
+
_meta: mcpToolUiMeta("Loading execution run details…", "Loaded execution run"),
|
|
773
1335
|
},
|
|
774
1336
|
{
|
|
775
1337
|
name: "cancel_run",
|
|
@@ -792,6 +1354,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
792
1354
|
destructiveHint: true,
|
|
793
1355
|
openWorldHint: false,
|
|
794
1356
|
},
|
|
1357
|
+
_meta: mcpToolUiMeta("Cancelling execution run…", "Cancelled execution run"),
|
|
795
1358
|
},
|
|
796
1359
|
{
|
|
797
1360
|
name: "get_receipt",
|
|
@@ -820,6 +1383,7 @@ export const MUXO_MCP_TOOLS = [
|
|
|
820
1383
|
destructiveHint: false,
|
|
821
1384
|
openWorldHint: false,
|
|
822
1385
|
},
|
|
1386
|
+
_meta: mcpToolUiMeta("Verifying Ed25519 & SHA-256 execution receipt…", "Verified cryptographic receipt"),
|
|
823
1387
|
},
|
|
824
1388
|
];
|
|
825
1389
|
/**
|
|
@@ -943,11 +1507,40 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
943
1507
|
break;
|
|
944
1508
|
}
|
|
945
1509
|
case "start_connection": {
|
|
946
|
-
const
|
|
1510
|
+
const conn = obj.connection && typeof obj.connection === "object" ? obj.connection : obj;
|
|
1511
|
+
const provider = String(conn.providerSlug || obj.providerSlug || toolArgs.provider || "<provider>");
|
|
1512
|
+
const providerName = String(conn.providerName || obj.providerName || provider);
|
|
1513
|
+
const redirectUrl = (conn.redirectUrl ?? obj.redirectUrl ?? null);
|
|
1514
|
+
const isStripeProjects = conn.lane === "stripe_projects" || isStripeProjectsExclusiveProvider(provider);
|
|
1515
|
+
if (isStripeProjects) {
|
|
1516
|
+
const canonicalSlug = resolveStripeProjectsCanonicalToolSlug(provider) || `resource.${provider}.project`;
|
|
1517
|
+
guidance = {
|
|
1518
|
+
summary: `${providerName} (${provider}) is connected via Stripe Projects Cloud Infrastructure (zero OAuth handoff required). Call plan_action with ${canonicalSlug} and execute_provisioning directly.`,
|
|
1519
|
+
nextToolCalls: [
|
|
1520
|
+
{
|
|
1521
|
+
tool: "plan_action",
|
|
1522
|
+
input: {
|
|
1523
|
+
tool_slug: canonicalSlug,
|
|
1524
|
+
input: { resource_name: `${provider}-service`, plan: "free" },
|
|
1525
|
+
reason: `Provision ${providerName} via Stripe Projects`,
|
|
1526
|
+
},
|
|
1527
|
+
when: "Immediately; Stripe Projects providers do not require OAuth handoff",
|
|
1528
|
+
},
|
|
1529
|
+
],
|
|
1530
|
+
};
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
if (redirectUrl && !obj.humanHandoff) {
|
|
1534
|
+
obj.humanHandoff = buildOAuthHumanHandoff({
|
|
1535
|
+
providerSlug: provider,
|
|
1536
|
+
providerName,
|
|
1537
|
+
redirectUrl,
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
947
1540
|
guidance = {
|
|
948
|
-
summary:
|
|
949
|
-
? `Share ${String(
|
|
950
|
-
: `Connection status for ${provider}: ${String(obj.status || "active")}.`,
|
|
1541
|
+
summary: redirectUrl
|
|
1542
|
+
? `Share ${String(redirectUrl)} with the user so they can authorize ${provider} with Muxo, then call wait_for_connection.`
|
|
1543
|
+
: `Connection status for ${provider}: ${String(conn.status || obj.status || "active")}.`,
|
|
951
1544
|
nextToolCalls: [
|
|
952
1545
|
{
|
|
953
1546
|
tool: "wait_for_connection",
|
|
@@ -960,9 +1553,37 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
960
1553
|
}
|
|
961
1554
|
case "wait_for_connection": {
|
|
962
1555
|
const provider = String(obj.provider || toolArgs.provider || "<provider>");
|
|
1556
|
+
const redirectUrl = (obj.redirectUrl ?? obj.connection?.redirectUrl ?? null);
|
|
1557
|
+
const isStripeProjects = obj.connection?.lane === "stripe_projects" ||
|
|
1558
|
+
isStripeProjectsExclusiveProvider(provider);
|
|
1559
|
+
if (isStripeProjects) {
|
|
1560
|
+
const canonicalSlug = resolveStripeProjectsCanonicalToolSlug(provider) || `resource.${provider}.project`;
|
|
1561
|
+
guidance = {
|
|
1562
|
+
summary: `${provider} is connected and active via Stripe Projects Cloud Infrastructure (zero OAuth required). Proceed to plan_action with ${canonicalSlug} and execute_provisioning.`,
|
|
1563
|
+
nextToolCalls: [
|
|
1564
|
+
{
|
|
1565
|
+
tool: "plan_action",
|
|
1566
|
+
input: {
|
|
1567
|
+
tool_slug: canonicalSlug,
|
|
1568
|
+
input: { resource_name: `${provider}-service`, plan: "free" },
|
|
1569
|
+
reason: `Provision ${provider} via Stripe Projects`,
|
|
1570
|
+
},
|
|
1571
|
+
when: "Now; Stripe Projects provider is active",
|
|
1572
|
+
},
|
|
1573
|
+
],
|
|
1574
|
+
};
|
|
1575
|
+
break;
|
|
1576
|
+
}
|
|
1577
|
+
if (!obj.connected && redirectUrl && !obj.humanHandoff) {
|
|
1578
|
+
obj.humanHandoff = buildOAuthHumanHandoff({
|
|
1579
|
+
providerSlug: provider,
|
|
1580
|
+
providerName: obj.connection?.providerName,
|
|
1581
|
+
redirectUrl,
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
963
1584
|
guidance = obj.connected
|
|
964
1585
|
? {
|
|
965
|
-
summary: `${provider} is connected and active. Proceed to plan_action or execute_<riskLevel>.`,
|
|
1586
|
+
summary: `${provider} is connected and active in Muxo. Proceed to plan_action or execute_<riskLevel>.`,
|
|
966
1587
|
nextToolCalls: [
|
|
967
1588
|
{
|
|
968
1589
|
tool: "plan_action",
|
|
@@ -976,7 +1597,7 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
976
1597
|
],
|
|
977
1598
|
}
|
|
978
1599
|
: {
|
|
979
|
-
summary: `Still awaiting user OAuth completion for ${provider}. Ask the user to open ${String(
|
|
1600
|
+
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
1601
|
nextToolCalls: [
|
|
981
1602
|
{
|
|
982
1603
|
tool: "wait_for_connection",
|
|
@@ -1020,6 +1641,80 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1020
1641
|
};
|
|
1021
1642
|
break;
|
|
1022
1643
|
}
|
|
1644
|
+
case "plan_action": {
|
|
1645
|
+
const plan = obj.plan || obj;
|
|
1646
|
+
const planId = String(plan?.id || obj.id || "<plan_id>");
|
|
1647
|
+
const risk = String(plan?.riskLevel || obj.riskLevel || "read");
|
|
1648
|
+
const status = String(plan?.approvalStatus || obj.approvalStatus || "auto_approved");
|
|
1649
|
+
const authHandoff = plan?.authHandoff || obj.authHandoff;
|
|
1650
|
+
if (authHandoff?.required) {
|
|
1651
|
+
const provider = String(authHandoff.providerSlug || "app");
|
|
1652
|
+
guidance = {
|
|
1653
|
+
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}.`,
|
|
1654
|
+
nextToolCalls: [
|
|
1655
|
+
{
|
|
1656
|
+
tool: "wait_for_connection",
|
|
1657
|
+
input: { provider, timeout_seconds: 30 },
|
|
1658
|
+
when: "While the user completes the browser OAuth flow",
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
tool: `execute_${risk}`,
|
|
1662
|
+
input: { plan_id: planId },
|
|
1663
|
+
when: "After OAuth connection is active and plan is approved",
|
|
1664
|
+
},
|
|
1665
|
+
],
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
else if (status === "approved" || status === "auto_approved") {
|
|
1669
|
+
guidance = {
|
|
1670
|
+
summary: `Plan ${planId} is ${status} under workspace policy. Call execute_${risk} to execute it now.`,
|
|
1671
|
+
nextToolCalls: [
|
|
1672
|
+
{
|
|
1673
|
+
tool: `execute_${risk}`,
|
|
1674
|
+
input: { plan_id: planId },
|
|
1675
|
+
when: "Immediately; plan passed workspace guardrails",
|
|
1676
|
+
},
|
|
1677
|
+
],
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
else if (status === "pending_approval") {
|
|
1681
|
+
const approvalUrl = obj.humanHandoff?.url ||
|
|
1682
|
+
plan?.humanHandoff?.url ||
|
|
1683
|
+
`/approve/${planId}`;
|
|
1684
|
+
guidance = {
|
|
1685
|
+
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).`,
|
|
1686
|
+
nextToolCalls: [
|
|
1687
|
+
{
|
|
1688
|
+
tool: "get_plan",
|
|
1689
|
+
input: {
|
|
1690
|
+
plan_id: planId,
|
|
1691
|
+
wait_for_approval: true,
|
|
1692
|
+
timeout_seconds: 25,
|
|
1693
|
+
},
|
|
1694
|
+
when: "After sharing the approval link with the user",
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
tool: `execute_${risk}`,
|
|
1698
|
+
input: { plan_id: planId },
|
|
1699
|
+
when: "Once get_plan reports approvalStatus='approved'",
|
|
1700
|
+
},
|
|
1701
|
+
],
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
guidance = {
|
|
1706
|
+
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.`,
|
|
1707
|
+
nextToolCalls: [
|
|
1708
|
+
{
|
|
1709
|
+
tool: "get_run_context",
|
|
1710
|
+
input: { action: "governance_status" },
|
|
1711
|
+
when: "To inspect active workspace guardrails and parameter rules",
|
|
1712
|
+
},
|
|
1713
|
+
],
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
break;
|
|
1717
|
+
}
|
|
1023
1718
|
case "execute_read":
|
|
1024
1719
|
case "execute_write":
|
|
1025
1720
|
case "execute_admin":
|
|
@@ -1041,26 +1736,44 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1041
1736
|
}
|
|
1042
1737
|
case "get_run_context":
|
|
1043
1738
|
case "set_guardrail":
|
|
1739
|
+
case "reset_guardrails":
|
|
1740
|
+
case "check_policy":
|
|
1741
|
+
case "kill_switch":
|
|
1742
|
+
case "governance_status":
|
|
1044
1743
|
case "configure_guardrails": {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1744
|
+
const act = String(obj.action || toolArgs.action || toolName || "inspect");
|
|
1745
|
+
if (act === "check_policy" && Array.isArray(obj.nextToolCalls) && obj.nextToolCalls.length > 0) {
|
|
1746
|
+
guidance = {
|
|
1747
|
+
summary: String(obj.remediationForLlm ||
|
|
1748
|
+
`Policy simulation complete: decision=${String(obj.decision || "auto_approved")}.`),
|
|
1749
|
+
nextToolCalls: obj.nextToolCalls,
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
guidance = {
|
|
1754
|
+
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.",
|
|
1755
|
+
nextToolCalls: [
|
|
1756
|
+
{
|
|
1757
|
+
tool: "get_run_context",
|
|
1758
|
+
input: {
|
|
1759
|
+
action: "check_policy",
|
|
1760
|
+
tool_slug: "instant.web.search",
|
|
1761
|
+
input: { query: "example" },
|
|
1762
|
+
},
|
|
1763
|
+
when: "To dry-run check if a tool + input will pass active guardrails before creating a plan",
|
|
1059
1764
|
},
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1765
|
+
{
|
|
1766
|
+
tool: "plan_action",
|
|
1767
|
+
input: {
|
|
1768
|
+
tool_slug: "<tool_slug>",
|
|
1769
|
+
input: {},
|
|
1770
|
+
reason: "Planned action under active workspace guardrails",
|
|
1771
|
+
},
|
|
1772
|
+
when: "To evaluate guardrails and prepare an action for execution",
|
|
1773
|
+
},
|
|
1774
|
+
],
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1064
1777
|
break;
|
|
1065
1778
|
}
|
|
1066
1779
|
default:
|
|
@@ -1074,3 +1787,794 @@ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
|
|
|
1074
1787
|
}
|
|
1075
1788
|
return output;
|
|
1076
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Canonical OpenAI Plugin UI (`text/html+skybridge`) & MCP Apps (`text/html;profile=mcp-app`)
|
|
1792
|
+
* widget resource descriptors exposed by `resources/list` and `resources/read`.
|
|
1793
|
+
* Follows:
|
|
1794
|
+
* - https://developers.openai.com/plugins/reference
|
|
1795
|
+
* - https://developers.openai.com/plugins/build/chatgpt-ui
|
|
1796
|
+
* - https://developers.openai.com/plugins/concepts/ui-guidelines
|
|
1797
|
+
*/
|
|
1798
|
+
export const MUXO_UI_CSP_DOMAINS = [
|
|
1799
|
+
"https://api.muxo.ai",
|
|
1800
|
+
"https://muxo.ai",
|
|
1801
|
+
"https://app.muxo.ai",
|
|
1802
|
+
];
|
|
1803
|
+
export const MUXO_UI_REDIRECT_DOMAINS = [
|
|
1804
|
+
"https://api.muxo.ai",
|
|
1805
|
+
"https://muxo.ai",
|
|
1806
|
+
"https://app.muxo.ai",
|
|
1807
|
+
"https://connect.composio.dev",
|
|
1808
|
+
"https://backend.composio.dev",
|
|
1809
|
+
];
|
|
1810
|
+
function buildMuxoUiResourceMeta(description) {
|
|
1811
|
+
return {
|
|
1812
|
+
ui: {
|
|
1813
|
+
prefersBorder: true,
|
|
1814
|
+
domain: "https://api.muxo.ai",
|
|
1815
|
+
csp: {
|
|
1816
|
+
connectDomains: [...MUXO_UI_CSP_DOMAINS],
|
|
1817
|
+
resourceDomains: [...MUXO_UI_CSP_DOMAINS],
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1820
|
+
"openai/ui": {
|
|
1821
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1822
|
+
},
|
|
1823
|
+
"openai/widgetDescription": description,
|
|
1824
|
+
"openai/widgetPrefersBorder": true,
|
|
1825
|
+
"openai/widgetDomain": "https://api.muxo.ai",
|
|
1826
|
+
"openai/widgetCSP": {
|
|
1827
|
+
connect_domains: [...MUXO_UI_CSP_DOMAINS],
|
|
1828
|
+
resource_domains: [...MUXO_UI_CSP_DOMAINS],
|
|
1829
|
+
redirect_domains: [...MUXO_UI_REDIRECT_DOMAINS],
|
|
1830
|
+
},
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
export const MUXO_UI_RESOURCES = [
|
|
1834
|
+
{
|
|
1835
|
+
uri: MUXO_UI_WIDGET_URI,
|
|
1836
|
+
name: "Muxo Action, Catalog & Receipt Card",
|
|
1837
|
+
title: "Muxo Action, Catalog & Receipt Card",
|
|
1838
|
+
description: "Interactive ChatGPT inline card, tool catalog carousel, OAuth/approval handoff CTA, and fullscreen cryptographic receipt & SEO inspector.",
|
|
1839
|
+
mimeType: "text/html+skybridge",
|
|
1840
|
+
_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."),
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
uri: MUXO_UI_MCP_APP_URI,
|
|
1844
|
+
name: "Muxo MCP App Widget",
|
|
1845
|
+
title: "Muxo MCP App Widget",
|
|
1846
|
+
description: "Standard MCP App HTML widget (text/html;profile=mcp-app) for Muxo plans, tool discovery carousels, and cryptographic receipt verification.",
|
|
1847
|
+
mimeType: "text/html;profile=mcp-app",
|
|
1848
|
+
_meta: buildMuxoUiResourceMeta("Standard MCP App HTML widget for Muxo action plans, OAuth handoffs, tool catalog carousels, and receipt verification."),
|
|
1849
|
+
},
|
|
1850
|
+
];
|
|
1851
|
+
/**
|
|
1852
|
+
* Renders the self-contained OpenAI Plugin UI / MCP App widget HTML (`ui://widget/muxo-card.html`).
|
|
1853
|
+
* Strictly adheres to https://developers.openai.com/plugins/concepts/ui-guidelines:
|
|
1854
|
+
* - System font stack (`-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif`)
|
|
1855
|
+
* - Light & dark mode CSS variables (`window.openai.theme` + `prefers-color-scheme`)
|
|
1856
|
+
* - Brand accent (`#6366F1` light / `#818CF8` dark) only on primary buttons & badges (WCAG AA)
|
|
1857
|
+
* - Monochromatic outlined SVG icons; no duplicate app logo header
|
|
1858
|
+
* - Auto-fitting inline card with no nested scrollbars (`notifyIntrinsicHeight`), max 2 bottom CTAs
|
|
1859
|
+
* - Inline 3–8 card carousel for `search_tools` catalog results
|
|
1860
|
+
* - Inline editable parameter controls for `plan_action` before clicking Execute
|
|
1861
|
+
* - Expand to fullscreen (`requestDisplayMode({ mode: "fullscreen" })`) for rich inspection
|
|
1862
|
+
*/
|
|
1863
|
+
export function renderMuxoUiWidgetHtml(baseUrl = "https://api.muxo.ai") {
|
|
1864
|
+
const safeBaseUrl = String(baseUrl || "https://api.muxo.ai").replace(/\/+$/, "");
|
|
1865
|
+
return `<!DOCTYPE html>
|
|
1866
|
+
<html lang="en">
|
|
1867
|
+
<head>
|
|
1868
|
+
<meta charset="utf-8" />
|
|
1869
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1870
|
+
<title>Muxo Action Card</title>
|
|
1871
|
+
<style>
|
|
1872
|
+
:root {
|
|
1873
|
+
color-scheme: light dark;
|
|
1874
|
+
--mx-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
1875
|
+
--mx-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
1876
|
+
--mx-bg: #ffffff;
|
|
1877
|
+
--mx-surface: #f7f7f8;
|
|
1878
|
+
--mx-border: rgba(13, 13, 13, 0.1);
|
|
1879
|
+
--mx-text: #0d0d0d;
|
|
1880
|
+
--mx-muted: #5d5d65;
|
|
1881
|
+
--mx-accent: #6366F1;
|
|
1882
|
+
--mx-accent-fg: #ffffff;
|
|
1883
|
+
--mx-badge-bg: rgba(99, 102, 241, 0.1);
|
|
1884
|
+
--mx-badge-fg: #4338ca;
|
|
1885
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.12);
|
|
1886
|
+
--mx-ok-fg: #047857;
|
|
1887
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.14);
|
|
1888
|
+
--mx-warn-fg: #b45309;
|
|
1889
|
+
--mx-err-bg: rgba(239, 68, 68, 0.12);
|
|
1890
|
+
--mx-err-fg: #b91c1c;
|
|
1891
|
+
}
|
|
1892
|
+
@media (prefers-color-scheme: dark) {
|
|
1893
|
+
:root:not([data-theme="light"]) {
|
|
1894
|
+
--mx-bg: #171717;
|
|
1895
|
+
--mx-surface: #212121;
|
|
1896
|
+
--mx-border: rgba(255, 255, 255, 0.12);
|
|
1897
|
+
--mx-text: #ececec;
|
|
1898
|
+
--mx-muted: #9b9ba4;
|
|
1899
|
+
--mx-accent: #818CF8;
|
|
1900
|
+
--mx-accent-fg: #0d0d0d;
|
|
1901
|
+
--mx-badge-bg: rgba(129, 140, 248, 0.16);
|
|
1902
|
+
--mx-badge-fg: #c7d2fe;
|
|
1903
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.18);
|
|
1904
|
+
--mx-ok-fg: #6ee7b7;
|
|
1905
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.2);
|
|
1906
|
+
--mx-warn-fg: #fcd34d;
|
|
1907
|
+
--mx-err-bg: rgba(239, 68, 68, 0.2);
|
|
1908
|
+
--mx-err-fg: #fca5a5;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
:root[data-theme="dark"] {
|
|
1912
|
+
--mx-bg: #171717;
|
|
1913
|
+
--mx-surface: #212121;
|
|
1914
|
+
--mx-border: rgba(255, 255, 255, 0.12);
|
|
1915
|
+
--mx-text: #ececec;
|
|
1916
|
+
--mx-muted: #9b9ba4;
|
|
1917
|
+
--mx-accent: #818CF8;
|
|
1918
|
+
--mx-accent-fg: #0d0d0d;
|
|
1919
|
+
--mx-badge-bg: rgba(129, 140, 248, 0.16);
|
|
1920
|
+
--mx-badge-fg: #c7d2fe;
|
|
1921
|
+
--mx-ok-bg: rgba(16, 185, 129, 0.18);
|
|
1922
|
+
--mx-ok-fg: #6ee7b7;
|
|
1923
|
+
--mx-warn-bg: rgba(245, 158, 11, 0.2);
|
|
1924
|
+
--mx-warn-fg: #fcd34d;
|
|
1925
|
+
--mx-err-bg: rgba(239, 68, 68, 0.2);
|
|
1926
|
+
--mx-err-fg: #fca5a5;
|
|
1927
|
+
}
|
|
1928
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1929
|
+
html, body {
|
|
1930
|
+
font-family: var(--mx-font);
|
|
1931
|
+
font-size: 14px;
|
|
1932
|
+
line-height: 1.45;
|
|
1933
|
+
color: var(--mx-text);
|
|
1934
|
+
background: transparent;
|
|
1935
|
+
-webkit-font-smoothing: antialiased;
|
|
1936
|
+
}
|
|
1937
|
+
body[data-mode="inline"] {
|
|
1938
|
+
overflow: hidden;
|
|
1939
|
+
}
|
|
1940
|
+
.mx-card {
|
|
1941
|
+
background: var(--mx-bg);
|
|
1942
|
+
border: 1px solid var(--mx-border);
|
|
1943
|
+
border-radius: 14px;
|
|
1944
|
+
padding: 16px;
|
|
1945
|
+
display: flex;
|
|
1946
|
+
flex-direction: column;
|
|
1947
|
+
gap: 12px;
|
|
1948
|
+
}
|
|
1949
|
+
.mx-header {
|
|
1950
|
+
display: flex;
|
|
1951
|
+
align-items: flex-start;
|
|
1952
|
+
justify-content: space-between;
|
|
1953
|
+
gap: 10px;
|
|
1954
|
+
}
|
|
1955
|
+
.mx-title-wrap {
|
|
1956
|
+
display: flex;
|
|
1957
|
+
align-items: center;
|
|
1958
|
+
gap: 8px;
|
|
1959
|
+
min-width: 0;
|
|
1960
|
+
}
|
|
1961
|
+
.mx-icon {
|
|
1962
|
+
width: 18px;
|
|
1963
|
+
height: 18px;
|
|
1964
|
+
flex-shrink: 0;
|
|
1965
|
+
color: var(--mx-muted);
|
|
1966
|
+
}
|
|
1967
|
+
.mx-title {
|
|
1968
|
+
font-size: 15px;
|
|
1969
|
+
font-weight: 600;
|
|
1970
|
+
white-space: nowrap;
|
|
1971
|
+
overflow: hidden;
|
|
1972
|
+
text-overflow: ellipsis;
|
|
1973
|
+
}
|
|
1974
|
+
.mx-subtitle {
|
|
1975
|
+
font-size: 12px;
|
|
1976
|
+
color: var(--mx-muted);
|
|
1977
|
+
margin-top: 2px;
|
|
1978
|
+
}
|
|
1979
|
+
.mx-header-right {
|
|
1980
|
+
display: flex;
|
|
1981
|
+
align-items: center;
|
|
1982
|
+
gap: 6px;
|
|
1983
|
+
flex-shrink: 0;
|
|
1984
|
+
}
|
|
1985
|
+
.mx-badge {
|
|
1986
|
+
display: inline-flex;
|
|
1987
|
+
align-items: center;
|
|
1988
|
+
gap: 4px;
|
|
1989
|
+
padding: 2px 8px;
|
|
1990
|
+
border-radius: 999px;
|
|
1991
|
+
font-size: 11px;
|
|
1992
|
+
font-weight: 600;
|
|
1993
|
+
letter-spacing: 0.01em;
|
|
1994
|
+
background: var(--mx-badge-bg);
|
|
1995
|
+
color: var(--mx-badge-fg);
|
|
1996
|
+
}
|
|
1997
|
+
.mx-badge.ok { background: var(--mx-ok-bg); color: var(--mx-ok-fg); }
|
|
1998
|
+
.mx-badge.warn { background: var(--mx-warn-bg); color: var(--mx-warn-fg); }
|
|
1999
|
+
.mx-badge.err { background: var(--mx-err-bg); color: var(--mx-err-fg); }
|
|
2000
|
+
.mx-expand-btn {
|
|
2001
|
+
background: transparent;
|
|
2002
|
+
border: 1px solid var(--mx-border);
|
|
2003
|
+
color: var(--mx-muted);
|
|
2004
|
+
border-radius: 8px;
|
|
2005
|
+
width: 26px;
|
|
2006
|
+
height: 26px;
|
|
2007
|
+
display: inline-flex;
|
|
2008
|
+
align-items: center;
|
|
2009
|
+
justify-content: center;
|
|
2010
|
+
cursor: pointer;
|
|
2011
|
+
}
|
|
2012
|
+
.mx-expand-btn:hover { color: var(--mx-text); background: var(--mx-surface); }
|
|
2013
|
+
.mx-grid {
|
|
2014
|
+
display: grid;
|
|
2015
|
+
grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
|
|
2016
|
+
gap: 8px;
|
|
2017
|
+
}
|
|
2018
|
+
.mx-stat {
|
|
2019
|
+
background: var(--mx-surface);
|
|
2020
|
+
border-radius: 10px;
|
|
2021
|
+
padding: 8px 10px;
|
|
2022
|
+
}
|
|
2023
|
+
.mx-stat-label {
|
|
2024
|
+
font-size: 11px;
|
|
2025
|
+
color: var(--mx-muted);
|
|
2026
|
+
}
|
|
2027
|
+
.mx-stat-value {
|
|
2028
|
+
font-size: 13px;
|
|
2029
|
+
font-weight: 600;
|
|
2030
|
+
margin-top: 2px;
|
|
2031
|
+
word-break: break-word;
|
|
2032
|
+
}
|
|
2033
|
+
.mx-mono { font-family: var(--mx-mono); font-size: 12px; }
|
|
2034
|
+
.mx-edit-row {
|
|
2035
|
+
display: flex;
|
|
2036
|
+
flex-direction: column;
|
|
2037
|
+
gap: 6px;
|
|
2038
|
+
background: var(--mx-surface);
|
|
2039
|
+
padding: 10px;
|
|
2040
|
+
border-radius: 10px;
|
|
2041
|
+
}
|
|
2042
|
+
.mx-edit-field {
|
|
2043
|
+
display: flex;
|
|
2044
|
+
align-items: center;
|
|
2045
|
+
justify-content: space-between;
|
|
2046
|
+
gap: 8px;
|
|
2047
|
+
}
|
|
2048
|
+
.mx-edit-field label {
|
|
2049
|
+
font-size: 12px;
|
|
2050
|
+
color: var(--mx-muted);
|
|
2051
|
+
min-width: 90px;
|
|
2052
|
+
}
|
|
2053
|
+
.mx-edit-field input {
|
|
2054
|
+
flex: 1;
|
|
2055
|
+
background: var(--mx-bg);
|
|
2056
|
+
color: var(--mx-text);
|
|
2057
|
+
border: 1px solid var(--mx-border);
|
|
2058
|
+
border-radius: 6px;
|
|
2059
|
+
padding: 4px 8px;
|
|
2060
|
+
font-family: var(--mx-font);
|
|
2061
|
+
font-size: 12px;
|
|
2062
|
+
}
|
|
2063
|
+
/* Inline Carousel (3-8 items per OpenAI UI Guidelines) */
|
|
2064
|
+
.mx-carousel {
|
|
2065
|
+
display: flex;
|
|
2066
|
+
gap: 10px;
|
|
2067
|
+
overflow-x: auto;
|
|
2068
|
+
scroll-snap-type: x mandatory;
|
|
2069
|
+
padding-bottom: 4px;
|
|
2070
|
+
scrollbar-width: none;
|
|
2071
|
+
}
|
|
2072
|
+
.mx-carousel::-webkit-scrollbar { display: none; }
|
|
2073
|
+
.mx-carousel-item {
|
|
2074
|
+
scroll-snap-align: start;
|
|
2075
|
+
flex: 0 0 210px;
|
|
2076
|
+
background: var(--mx-surface);
|
|
2077
|
+
border: 1px solid var(--mx-border);
|
|
2078
|
+
border-radius: 12px;
|
|
2079
|
+
padding: 12px;
|
|
2080
|
+
display: flex;
|
|
2081
|
+
flex-direction: column;
|
|
2082
|
+
justify-content: space-between;
|
|
2083
|
+
gap: 8px;
|
|
2084
|
+
}
|
|
2085
|
+
.mx-carousel-title {
|
|
2086
|
+
font-size: 13px;
|
|
2087
|
+
font-weight: 600;
|
|
2088
|
+
white-space: nowrap;
|
|
2089
|
+
overflow: hidden;
|
|
2090
|
+
text-overflow: ellipsis;
|
|
2091
|
+
}
|
|
2092
|
+
.mx-carousel-desc {
|
|
2093
|
+
font-size: 12px;
|
|
2094
|
+
color: var(--mx-muted);
|
|
2095
|
+
display: -webkit-box;
|
|
2096
|
+
-webkit-line-clamp: 2;
|
|
2097
|
+
-webkit-box-orient: vertical;
|
|
2098
|
+
overflow: hidden;
|
|
2099
|
+
}
|
|
2100
|
+
/* Max 2 bottom actions per Inline Card */
|
|
2101
|
+
.mx-actions {
|
|
2102
|
+
display: flex;
|
|
2103
|
+
align-items: center;
|
|
2104
|
+
justify-content: flex-end;
|
|
2105
|
+
gap: 8px;
|
|
2106
|
+
padding-top: 4px;
|
|
2107
|
+
}
|
|
2108
|
+
.mx-btn {
|
|
2109
|
+
appearance: none;
|
|
2110
|
+
border-radius: 8px;
|
|
2111
|
+
padding: 7px 12px;
|
|
2112
|
+
font-family: var(--mx-font);
|
|
2113
|
+
font-size: 13px;
|
|
2114
|
+
font-weight: 600;
|
|
2115
|
+
cursor: pointer;
|
|
2116
|
+
border: 1px solid transparent;
|
|
2117
|
+
display: inline-flex;
|
|
2118
|
+
align-items: center;
|
|
2119
|
+
justify-content: center;
|
|
2120
|
+
gap: 6px;
|
|
2121
|
+
transition: opacity 0.15s ease;
|
|
2122
|
+
}
|
|
2123
|
+
.mx-btn:disabled { opacity: 0.55; cursor: default; }
|
|
2124
|
+
.mx-btn-primary {
|
|
2125
|
+
background: var(--mx-accent);
|
|
2126
|
+
color: var(--mx-accent-fg);
|
|
2127
|
+
}
|
|
2128
|
+
.mx-btn-secondary {
|
|
2129
|
+
background: transparent;
|
|
2130
|
+
border-color: var(--mx-border);
|
|
2131
|
+
color: var(--mx-text);
|
|
2132
|
+
}
|
|
2133
|
+
.mx-fullscreen-details {
|
|
2134
|
+
display: none;
|
|
2135
|
+
background: var(--mx-surface);
|
|
2136
|
+
border-radius: 10px;
|
|
2137
|
+
padding: 10px;
|
|
2138
|
+
font-family: var(--mx-mono);
|
|
2139
|
+
font-size: 11px;
|
|
2140
|
+
white-space: pre-wrap;
|
|
2141
|
+
word-break: break-all;
|
|
2142
|
+
}
|
|
2143
|
+
body[data-mode="fullscreen"] .mx-fullscreen-details {
|
|
2144
|
+
display: block;
|
|
2145
|
+
}
|
|
2146
|
+
</style>
|
|
2147
|
+
</head>
|
|
2148
|
+
<body data-mode="inline">
|
|
2149
|
+
<div id="mx-root" class="mx-card" role="region" aria-label="Muxo Action Widget"></div>
|
|
2150
|
+
<script>
|
|
2151
|
+
(function () {
|
|
2152
|
+
const BASE_URL = ${JSON.stringify(safeBaseUrl)};
|
|
2153
|
+
const root = document.getElementById("mx-root");
|
|
2154
|
+
const pendingRequests = new Map();
|
|
2155
|
+
let nextRequestId = 1;
|
|
2156
|
+
let currentData = null;
|
|
2157
|
+
let latestToolInput = null;
|
|
2158
|
+
let editedInputs = {};
|
|
2159
|
+
|
|
2160
|
+
function mcpRpcRequest(method, params) {
|
|
2161
|
+
if (!window.parent || window.parent === window) {
|
|
2162
|
+
return Promise.resolve(null);
|
|
2163
|
+
}
|
|
2164
|
+
const id = nextRequestId++;
|
|
2165
|
+
window.parent.postMessage({ jsonrpc: "2.0", id, method: method, params: params || {} }, "*");
|
|
2166
|
+
return new Promise(function (resolve, reject) {
|
|
2167
|
+
pendingRequests.set(id, { resolve: resolve, reject: reject });
|
|
2168
|
+
setTimeout(function () {
|
|
2169
|
+
if (pendingRequests.has(id)) {
|
|
2170
|
+
pendingRequests.delete(id);
|
|
2171
|
+
resolve(null);
|
|
2172
|
+
}
|
|
2173
|
+
}, 15000);
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
function persistWidgetState(modelContent, privateContent) {
|
|
2178
|
+
const snapshot = {
|
|
2179
|
+
modelContent: modelContent,
|
|
2180
|
+
privateContent: privateContent || {},
|
|
2181
|
+
};
|
|
2182
|
+
if (window.openai && typeof window.openai.setWidgetState === "function") {
|
|
2183
|
+
window.openai.setWidgetState(snapshot);
|
|
2184
|
+
}
|
|
2185
|
+
mcpRpcRequest("ui/update-model-context", { context: modelContent });
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
function syncThemeAndMode() {
|
|
2189
|
+
const oa = window.openai || {};
|
|
2190
|
+
if (oa.theme === "dark" || oa.theme === "light") {
|
|
2191
|
+
document.documentElement.setAttribute("data-theme", oa.theme);
|
|
2192
|
+
}
|
|
2193
|
+
if (oa.locale && typeof oa.locale === "string") {
|
|
2194
|
+
document.documentElement.lang = oa.locale;
|
|
2195
|
+
}
|
|
2196
|
+
const mode = oa.displayMode === "fullscreen" ? "fullscreen" : "inline";
|
|
2197
|
+
document.body.setAttribute("data-mode", mode);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
function notifyHeight() {
|
|
2201
|
+
if (window.openai && typeof window.openai.notifyIntrinsicHeight === "function") {
|
|
2202
|
+
window.openai.notifyIntrinsicHeight(document.body.scrollHeight);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function esc(val) {
|
|
2207
|
+
return String(val == null ? "" : val)
|
|
2208
|
+
.replace(/&/g, "&")
|
|
2209
|
+
.replace(/</g, "<")
|
|
2210
|
+
.replace(/>/g, ">")
|
|
2211
|
+
.replace(/"/g, """);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function resolveUrl(href) {
|
|
2215
|
+
if (!href) return "";
|
|
2216
|
+
return href.startsWith("http") ? href : BASE_URL + (href.startsWith("/") ? href : "/" + href);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
function openUrl(href) {
|
|
2220
|
+
const target = resolveUrl(href);
|
|
2221
|
+
if (!target) return;
|
|
2222
|
+
if (window.openai && typeof window.openai.openExternal === "function") {
|
|
2223
|
+
window.openai.openExternal({ href: target });
|
|
2224
|
+
} else {
|
|
2225
|
+
window.open(target, "_blank", "noopener,noreferrer");
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
function setOpenInAppTarget(href) {
|
|
2230
|
+
const target = resolveUrl(href);
|
|
2231
|
+
if (target && window.openai && typeof window.openai.setOpenInAppUrl === "function") {
|
|
2232
|
+
window.openai.setOpenInAppUrl({ href: target });
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
async function invokeTool(name, args, btn) {
|
|
2237
|
+
if (btn) btn.disabled = true;
|
|
2238
|
+
try {
|
|
2239
|
+
if (window.openai && typeof window.openai.callTool === "function") {
|
|
2240
|
+
const res = await window.openai.callTool(name, args || {});
|
|
2241
|
+
if (res && res.structuredContent) {
|
|
2242
|
+
render(res.structuredContent);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
const rpcRes = await mcpRpcRequest("tools/call", {
|
|
2247
|
+
name: name,
|
|
2248
|
+
arguments: args || {},
|
|
2249
|
+
});
|
|
2250
|
+
if (rpcRes && rpcRes.structuredContent) {
|
|
2251
|
+
render(rpcRes.structuredContent);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
if (window.openai && typeof window.openai.sendFollowUpMessage === "function") {
|
|
2255
|
+
await window.openai.sendFollowUpMessage({
|
|
2256
|
+
prompt: "Please call " + name + " with " + JSON.stringify(args || {})
|
|
2257
|
+
});
|
|
2258
|
+
} else {
|
|
2259
|
+
await mcpRpcRequest("ui/message", {
|
|
2260
|
+
role: "user",
|
|
2261
|
+
content: { type: "text", text: "Please call " + name + " with " + JSON.stringify(args || {}) },
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
} finally {
|
|
2265
|
+
if (btn) btn.disabled = false;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
function toggleFullscreen() {
|
|
2270
|
+
const next = document.body.getAttribute("data-mode") === "fullscreen" ? "inline" : "fullscreen";
|
|
2271
|
+
document.body.setAttribute("data-mode", next);
|
|
2272
|
+
if (window.openai && typeof window.openai.requestDisplayMode === "function") {
|
|
2273
|
+
window.openai.requestDisplayMode({ mode: next });
|
|
2274
|
+
}
|
|
2275
|
+
notifyHeight();
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function render(data) {
|
|
2279
|
+
syncThemeAndMode();
|
|
2280
|
+
const oa = window.openai || {};
|
|
2281
|
+
const metaResult =
|
|
2282
|
+
oa.toolResponseMetadata &&
|
|
2283
|
+
oa.toolResponseMetadata.mcp_tool_result &&
|
|
2284
|
+
oa.toolResponseMetadata.mcp_tool_result.structuredContent;
|
|
2285
|
+
currentData = data || oa.toolOutput || metaResult || {};
|
|
2286
|
+
|
|
2287
|
+
// 1. Inline Carousel for search_tools (3-8 cards per OpenAI UI Guidelines)
|
|
2288
|
+
if (Array.isArray(currentData.tools) && currentData.tools.length > 0) {
|
|
2289
|
+
const items = currentData.tools.slice(0, 8);
|
|
2290
|
+
root.innerHTML =
|
|
2291
|
+
'<div class="mx-header">' +
|
|
2292
|
+
'<div class="mx-title-wrap">' +
|
|
2293
|
+
'<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>' +
|
|
2294
|
+
'<div><div class="mx-title">Unified Tool Catalog</div>' +
|
|
2295
|
+
'<div class="mx-subtitle">' + esc(currentData.count || items.length) + ' governed capabilities available</div></div>' +
|
|
2296
|
+
'</div>' +
|
|
2297
|
+
'<span class="mx-badge">' + esc(items.length) + ' shown</span>' +
|
|
2298
|
+
'</div>' +
|
|
2299
|
+
'<div class="mx-carousel">' +
|
|
2300
|
+
items.map(function (t) {
|
|
2301
|
+
const badgeClass = t.riskLevel === "read" ? "ok" : (t.riskLevel === "destructive" || t.riskLevel === "financial" ? "warn" : "");
|
|
2302
|
+
return (
|
|
2303
|
+
'<div class="mx-carousel-item">' +
|
|
2304
|
+
'<div>' +
|
|
2305
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;gap:6px;margin-bottom:4px;">' +
|
|
2306
|
+
'<span class="mx-badge ' + badgeClass + '">' + esc(t.riskLevel || "read") + '</span>' +
|
|
2307
|
+
'<span class="mx-subtitle mx-mono">' + esc(t.creditCost || 1) + ' cr</span>' +
|
|
2308
|
+
'</div>' +
|
|
2309
|
+
'<div class="mx-carousel-title" title="' + esc(t.name || t.slug) + '">' + esc(t.name || t.slug) + '</div>' +
|
|
2310
|
+
'<div class="mx-subtitle mx-mono" style="margin-bottom:4px;">' + esc(t.slug) + '</div>' +
|
|
2311
|
+
'<div class="mx-carousel-desc">' + esc(t.description || "") + '</div>' +
|
|
2312
|
+
'</div>' +
|
|
2313
|
+
'<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>' +
|
|
2314
|
+
'</div>'
|
|
2315
|
+
);
|
|
2316
|
+
}).join("") +
|
|
2317
|
+
'</div>';
|
|
2318
|
+
root.querySelectorAll("[data-plan-slug]").forEach(function (btn) {
|
|
2319
|
+
btn.addEventListener("click", function () {
|
|
2320
|
+
const slug = btn.getAttribute("data-plan-slug");
|
|
2321
|
+
persistWidgetState("Selected tool " + slug + " for planning.", { selectedToolSlug: slug });
|
|
2322
|
+
invokeTool("plan_action", { tool_slug: slug, input: {} }, btn);
|
|
2323
|
+
});
|
|
2324
|
+
});
|
|
2325
|
+
notifyHeight();
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// 2. Determine Inline Card state (Plan, Handoff, Execution Run, Receipt, or Workspace Summary)
|
|
2330
|
+
const plan = currentData.plan || (currentData.id && String(currentData.id).startsWith("plan_") ? currentData : null);
|
|
2331
|
+
const run = currentData.run || (currentData.id && String(currentData.id).startsWith("run_") ? currentData : null);
|
|
2332
|
+
const receipt = currentData.receipt || (currentData.id && String(currentData.id).startsWith("rcpt_") ? currentData : null);
|
|
2333
|
+
const handoff = currentData.humanHandoff || (plan && plan.humanHandoff) || null;
|
|
2334
|
+
const authHandoff = currentData.authHandoff || (plan && plan.authHandoff) || null;
|
|
2335
|
+
const handoffUrl =
|
|
2336
|
+
(handoff && (handoff.url || handoff.actionUrl || handoff.oauthRedirectUrl || handoff.approvalUrl)) ||
|
|
2337
|
+
(authHandoff && authHandoff.redirectUrl) ||
|
|
2338
|
+
currentData.redirectUrl ||
|
|
2339
|
+
null;
|
|
2340
|
+
const isHandoffActive = Boolean(
|
|
2341
|
+
(handoff && (handoff.required !== false && (handoffUrl || handoff.kind))) ||
|
|
2342
|
+
(authHandoff && authHandoff.required)
|
|
2343
|
+
);
|
|
2344
|
+
|
|
2345
|
+
let title = "Muxo Governed Operation";
|
|
2346
|
+
let subtitle = "Cryptographically audited agent execution";
|
|
2347
|
+
let statusLabel = "Ready";
|
|
2348
|
+
let statusTone = "ok";
|
|
2349
|
+
let stats = [];
|
|
2350
|
+
let editFieldsHtml = "";
|
|
2351
|
+
let primaryBtn = null;
|
|
2352
|
+
let secondaryBtn = null;
|
|
2353
|
+
|
|
2354
|
+
if (isHandoffActive && !run) {
|
|
2355
|
+
const isOauth =
|
|
2356
|
+
(handoff && (handoff.kind === "oauth" || handoff.kind === "oauth_connection")) ||
|
|
2357
|
+
Boolean(authHandoff && authHandoff.required);
|
|
2358
|
+
title =
|
|
2359
|
+
(handoff && (handoff.headline || handoff.title)) ||
|
|
2360
|
+
(isOauth ? "OAuth Connection Required" : "Human Approval Required");
|
|
2361
|
+
subtitle =
|
|
2362
|
+
(handoff && (handoff.instructions || handoff.summary)) ||
|
|
2363
|
+
"Complete this step in your browser to resume governed execution";
|
|
2364
|
+
statusLabel = isOauth ? "OAuth Handoff" : "Approval Needed";
|
|
2365
|
+
statusTone = "warn";
|
|
2366
|
+
const prov =
|
|
2367
|
+
(handoff && handoff.provider) ||
|
|
2368
|
+
(authHandoff && authHandoff.providerSlug) ||
|
|
2369
|
+
currentData.provider ||
|
|
2370
|
+
currentData.providerSlug ||
|
|
2371
|
+
(plan && plan.providerSlug);
|
|
2372
|
+
stats.push({
|
|
2373
|
+
label: "Provider / Tool",
|
|
2374
|
+
value: (plan && plan.toolSlug) || prov || "External Service"
|
|
2375
|
+
});
|
|
2376
|
+
if (plan && plan.id) stats.push({ label: "Plan ID", value: plan.id, mono: true });
|
|
2377
|
+
if (handoffUrl) {
|
|
2378
|
+
setOpenInAppTarget(handoffUrl);
|
|
2379
|
+
primaryBtn = {
|
|
2380
|
+
label: (handoff && handoff.ctaLabel) || (isOauth ? "Connect Account" : "Review & Approve"),
|
|
2381
|
+
onClick: function () { openUrl(handoffUrl); }
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
if (isOauth && prov) {
|
|
2385
|
+
secondaryBtn = {
|
|
2386
|
+
label: "Verify Connection",
|
|
2387
|
+
onClick: function (btn) { invokeTool("wait_for_connection", { provider: prov, timeout_seconds: 15 }, btn); }
|
|
2388
|
+
};
|
|
2389
|
+
} else if (plan && plan.id) {
|
|
2390
|
+
secondaryBtn = {
|
|
2391
|
+
label: "Check Status",
|
|
2392
|
+
onClick: function (btn) { invokeTool("get_plan", { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 10 }, btn); }
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
} else if (plan && !run) {
|
|
2396
|
+
title = plan.toolName || plan.toolSlug || "Governed Action Plan";
|
|
2397
|
+
subtitle = plan.policyReason || "Evaluated against workspace guardrails";
|
|
2398
|
+
const approved = plan.approvalStatus === "approved" || plan.approvalStatus === "auto_approved";
|
|
2399
|
+
statusLabel = approved ? "Approved (" + (plan.riskLevel || "read") + ")" : String(plan.approvalStatus || "planned");
|
|
2400
|
+
statusTone = approved ? "ok" : "warn";
|
|
2401
|
+
stats.push({ label: "Plan ID", value: plan.id, mono: true });
|
|
2402
|
+
stats.push({ label: "Lane", value: plan.lane || "composio_instant" });
|
|
2403
|
+
stats.push({ label: "Estimated Cost", value: String(plan.estimatedCreditCost || 1) + " credits" });
|
|
2404
|
+
|
|
2405
|
+
const inputObj =
|
|
2406
|
+
(plan.input && typeof plan.input === "object")
|
|
2407
|
+
? plan.input
|
|
2408
|
+
: (latestToolInput && latestToolInput.input && typeof latestToolInput.input === "object")
|
|
2409
|
+
? latestToolInput.input
|
|
2410
|
+
: {};
|
|
2411
|
+
const keys = Object.keys(inputObj).slice(0, 3);
|
|
2412
|
+
editedInputs = {};
|
|
2413
|
+
if (keys.length > 0 && approved) {
|
|
2414
|
+
editFieldsHtml = '<div class="mx-edit-row">' +
|
|
2415
|
+
keys.map(function (k) {
|
|
2416
|
+
const val = typeof inputObj[k] === "object" ? JSON.stringify(inputObj[k]) : String(inputObj[k] ?? "");
|
|
2417
|
+
return '<div class="mx-edit-field"><label>' + esc(k) + '</label><input data-input-key="' + esc(k) + '" value="' + esc(val) + '" /></div>';
|
|
2418
|
+
}).join("") +
|
|
2419
|
+
'</div>';
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
if (approved) {
|
|
2423
|
+
const execTool = "execute_" + String(plan.riskLevel || "read");
|
|
2424
|
+
primaryBtn = {
|
|
2425
|
+
label: "Execute Plan",
|
|
2426
|
+
onClick: function (btn) {
|
|
2427
|
+
if (Object.keys(editedInputs).length > 0 && plan.toolSlug) {
|
|
2428
|
+
const merged = Object.assign({}, inputObj, editedInputs);
|
|
2429
|
+
invokeTool("plan_action", { tool_slug: plan.toolSlug, input: merged, reason: plan.reason || "Updated parameters in widget" }, btn);
|
|
2430
|
+
} else {
|
|
2431
|
+
invokeTool(execTool, { plan_id: plan.id }, btn);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
} else if (plan.approvalUrl) {
|
|
2436
|
+
setOpenInAppTarget(plan.approvalUrl);
|
|
2437
|
+
primaryBtn = {
|
|
2438
|
+
label: "Approve Plan",
|
|
2439
|
+
onClick: function () { openUrl(plan.approvalUrl); }
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
secondaryBtn = {
|
|
2443
|
+
label: "Refresh Plan",
|
|
2444
|
+
onClick: function (btn) { invokeTool("get_plan", { plan_id: plan.id }, btn); }
|
|
2445
|
+
};
|
|
2446
|
+
} else if (run || receipt) {
|
|
2447
|
+
const r = run || {};
|
|
2448
|
+
const rc = receipt || {};
|
|
2449
|
+
title = r.toolSlug || rc.toolSlug || "Execution Receipt";
|
|
2450
|
+
subtitle = (rc.agenticReceipt && rc.agenticReceipt.publicKeyId)
|
|
2451
|
+
? "Signed Ed25519 (" + rc.agenticReceipt.publicKeyId + ") + SHA-256 Chain"
|
|
2452
|
+
: "Cryptographically verified execution record";
|
|
2453
|
+
const ok = (r.status || rc.status || "succeeded") === "succeeded";
|
|
2454
|
+
statusLabel = ok ? "Verified" : String(r.status || rc.status || "completed");
|
|
2455
|
+
statusTone = ok ? "ok" : "err";
|
|
2456
|
+
if (r.id || rc.runId) stats.push({ label: "Run ID", value: r.id || rc.runId, mono: true });
|
|
2457
|
+
if (rc.id) stats.push({ label: "Receipt ID", value: rc.id, mono: true });
|
|
2458
|
+
if (rc.outputHash) stats.push({ label: "Output SHA-256", value: String(rc.outputHash).slice(0, 16) + "…", mono: true });
|
|
2459
|
+
|
|
2460
|
+
const out = r.output || {};
|
|
2461
|
+
const liveUrl = out.liveUrl || out.deploymentUrl || (out.result && (out.result.liveUrl || out.result.url));
|
|
2462
|
+
if (liveUrl) {
|
|
2463
|
+
setOpenInAppTarget(liveUrl);
|
|
2464
|
+
primaryBtn = { label: "Open Live URL", onClick: function () { openUrl(liveUrl); } };
|
|
2465
|
+
} else if (r.id || rc.id) {
|
|
2466
|
+
primaryBtn = {
|
|
2467
|
+
label: "Verify Receipt",
|
|
2468
|
+
onClick: function (btn) { invokeTool("get_receipt", { receipt_id: rc.id || r.id, verify_chain: true }, btn); }
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
} else {
|
|
2472
|
+
title = (currentData.workspace && currentData.workspace.name) || "Muxo Workspace";
|
|
2473
|
+
subtitle = "Bi-temporal memory, policy guardrails & execution lanes active";
|
|
2474
|
+
statusLabel = "Active";
|
|
2475
|
+
statusTone = "ok";
|
|
2476
|
+
if (currentData.workspace && currentData.workspace.id) {
|
|
2477
|
+
stats.push({ label: "Workspace", value: currentData.workspace.id, mono: true });
|
|
2478
|
+
}
|
|
2479
|
+
if (currentData.billing && currentData.billing.creditsRemaining != null) {
|
|
2480
|
+
stats.push({ label: "Credits", value: String(currentData.billing.creditsRemaining) });
|
|
2481
|
+
}
|
|
2482
|
+
primaryBtn = {
|
|
2483
|
+
label: "Browse Tool Catalog",
|
|
2484
|
+
onClick: function (btn) { invokeTool("search_tools", { query: "" }, btn); }
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
const expandIcon =
|
|
2489
|
+
'<button class="mx-expand-btn" id="mx-expand" title="Toggle Fullscreen Inspector" aria-label="Toggle Fullscreen Inspector">' +
|
|
2490
|
+
'<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>' +
|
|
2491
|
+
'</button>';
|
|
2492
|
+
|
|
2493
|
+
root.innerHTML =
|
|
2494
|
+
'<div class="mx-header">' +
|
|
2495
|
+
'<div class="mx-title-wrap">' +
|
|
2496
|
+
'<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>' +
|
|
2497
|
+
'<div><div class="mx-title">' + esc(title) + '</div>' +
|
|
2498
|
+
'<div class="mx-subtitle">' + esc(subtitle) + '</div></div>' +
|
|
2499
|
+
'</div>' +
|
|
2500
|
+
'<div class="mx-header-right">' +
|
|
2501
|
+
'<span class="mx-badge ' + statusTone + '">' + esc(statusLabel) + '</span>' +
|
|
2502
|
+
expandIcon +
|
|
2503
|
+
'</div>' +
|
|
2504
|
+
'</div>' +
|
|
2505
|
+
(stats.length > 0
|
|
2506
|
+
? '<div class="mx-grid">' +
|
|
2507
|
+
stats.map(function (s) {
|
|
2508
|
+
return '<div class="mx-stat"><div class="mx-stat-label">' + esc(s.label) + '</div>' +
|
|
2509
|
+
'<div class="mx-stat-value ' + (s.mono ? 'mx-mono' : '') + '">' + esc(s.value) + '</div></div>';
|
|
2510
|
+
}).join("") +
|
|
2511
|
+
'</div>'
|
|
2512
|
+
: '') +
|
|
2513
|
+
editFieldsHtml +
|
|
2514
|
+
'<div class="mx-fullscreen-details">' + esc(JSON.stringify(currentData, null, 2)) + '</div>' +
|
|
2515
|
+
((primaryBtn || secondaryBtn)
|
|
2516
|
+
? '<div class="mx-actions">' +
|
|
2517
|
+
(secondaryBtn ? '<button class="mx-btn mx-btn-secondary" id="mx-btn-sec">' + esc(secondaryBtn.label) + '</button>' : '') +
|
|
2518
|
+
(primaryBtn ? '<button class="mx-btn mx-btn-primary" id="mx-btn-pri">' + esc(primaryBtn.label) + '</button>' : '') +
|
|
2519
|
+
'</div>'
|
|
2520
|
+
: '');
|
|
2521
|
+
|
|
2522
|
+
const expBtn = document.getElementById("mx-expand");
|
|
2523
|
+
if (expBtn) expBtn.addEventListener("click", toggleFullscreen);
|
|
2524
|
+
const priEl = document.getElementById("mx-btn-pri");
|
|
2525
|
+
if (priEl && primaryBtn) priEl.addEventListener("click", function () { primaryBtn.onClick(priEl); });
|
|
2526
|
+
const secEl = document.getElementById("mx-btn-sec");
|
|
2527
|
+
if (secEl && secondaryBtn) secEl.addEventListener("click", function () { secondaryBtn.onClick(secEl); });
|
|
2528
|
+
|
|
2529
|
+
root.querySelectorAll("[data-input-key]").forEach(function (inp) {
|
|
2530
|
+
inp.addEventListener("change", function () {
|
|
2531
|
+
const key = inp.getAttribute("data-input-key");
|
|
2532
|
+
if (!key) return;
|
|
2533
|
+
editedInputs[key] = inp.value;
|
|
2534
|
+
if (priEl) priEl.textContent = "Re-Plan & Execute";
|
|
2535
|
+
persistWidgetState(
|
|
2536
|
+
"User edited plan input parameter " + key + " = " + inp.value,
|
|
2537
|
+
{ planId: plan && plan.id, editedInputs: editedInputs }
|
|
2538
|
+
);
|
|
2539
|
+
});
|
|
2540
|
+
});
|
|
2541
|
+
|
|
2542
|
+
notifyHeight();
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
window.addEventListener("openai:set_globals", function () { render(currentData); });
|
|
2546
|
+
window.addEventListener("message", function (ev) {
|
|
2547
|
+
if (ev.source && window.parent && ev.source !== window.parent) return;
|
|
2548
|
+
const msg = ev && ev.data;
|
|
2549
|
+
if (!msg || msg.jsonrpc !== "2.0") return;
|
|
2550
|
+
if (msg.id !== undefined && pendingRequests.has(msg.id)) {
|
|
2551
|
+
const pending = pendingRequests.get(msg.id);
|
|
2552
|
+
pendingRequests.delete(msg.id);
|
|
2553
|
+
if (msg.error) pending.reject(msg.error);
|
|
2554
|
+
else pending.resolve(msg.result);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (msg.method === "ui/notifications/tool-input") {
|
|
2558
|
+
latestToolInput = msg.params || null;
|
|
2559
|
+
if (currentData) render(currentData);
|
|
2560
|
+
}
|
|
2561
|
+
if (msg.method === "ui/notifications/tool-result" && msg.params) {
|
|
2562
|
+
render(msg.params.structuredContent || msg.params);
|
|
2563
|
+
}
|
|
2564
|
+
});
|
|
2565
|
+
|
|
2566
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2567
|
+
new ResizeObserver(notifyHeight).observe(document.body);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
mcpRpcRequest("ui/initialize", {
|
|
2571
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
latestToolInput = (window.openai && window.openai.toolInput) || null;
|
|
2575
|
+
render((window.openai && window.openai.toolOutput) || null);
|
|
2576
|
+
})();
|
|
2577
|
+
</script>
|
|
2578
|
+
</body>
|
|
2579
|
+
</html>`;
|
|
2580
|
+
}
|