@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.
Files changed (43) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/README.md +1 -1
  4. package/dist/cli.js +6 -3
  5. package/dist/composio-catalog.d.ts +177 -1
  6. package/dist/composio-catalog.js +1427 -7
  7. package/dist/config.d.ts +6 -0
  8. package/dist/config.js +11 -1
  9. package/dist/engine.d.ts +18 -1
  10. package/dist/engine.js +259 -35
  11. package/dist/mcp-schema.d.ts +162 -1
  12. package/dist/mcp-schema.js +1523 -40
  13. package/dist/mcp.d.ts +2 -2
  14. package/dist/mcp.js +412 -23
  15. package/dist/policy.d.ts +108 -1
  16. package/dist/policy.js +406 -1
  17. package/dist/providers/composio-instant.js +38 -3
  18. package/dist/providers/composio-sdk.d.ts +59 -2
  19. package/dist/providers/composio-sdk.js +273 -59
  20. package/dist/providers/stripe-projects.d.ts +4 -0
  21. package/dist/providers/stripe-projects.js +548 -126
  22. package/dist/server.js +531 -61
  23. package/dist/store.js +2 -0
  24. package/dist/types.d.ts +15 -0
  25. package/dist/ui/console-html.d.ts +31 -1
  26. package/dist/ui/console-html.js +922 -368
  27. package/dist/ui/design-tokens.js +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/package.json +3 -2
  31. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  32. package/plugins/muxo/.codex-plugin/plugin.json +15 -8
  33. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  34. package/plugins/muxo/hooks/session-start.sh +15 -3
  35. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  36. package/plugins/muxo/mcp.json +0 -10
  37. package/plugins/muxo/plugin.json +4 -4
  38. package/plugins/muxo/skills/muxo/SKILL.md +25 -16
  39. package/plugins/muxo/skills/muxo/references/cli.md +5 -1
  40. package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
  41. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  42. package/plugins/muxo/skills/setup/SKILL.md +1 -1
  43. package/src/catalogues/stripe-projects-catalog.json +140 -0
@@ -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 or link a Cloud Infrastructure provider.",
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: "Provider or toolkit slug (e.g. 'github', 'gmail', 'slack', 'neon')",
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 (defaults to composio_sdk unless prefixed with resource.)",
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 provider = String(obj.providerSlug || toolArgs.provider || "<provider>");
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: obj.redirectUrl
949
- ? `Share ${String(obj.redirectUrl)} with the user so they can authorize ${provider}, then call wait_for_connection.`
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(obj.redirectUrl || "the authorization URL")} and call wait_for_connection again.`,
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
- guidance = {
1046
- summary: "Workspace context, memory defaults, and guardrails updated/loaded. Use plan_action to propose governed actions or get_run_context with action='set_guardrail'|'remember'|'forget' to update workspace state.",
1047
- nextToolCalls: [
1048
- {
1049
- tool: "search_tools",
1050
- input: { query: "" },
1051
- when: "To find tools matching the user's goal",
1052
- },
1053
- {
1054
- tool: "plan_action",
1055
- input: {
1056
- tool_slug: "<tool_slug>",
1057
- input: {},
1058
- reason: "Planned action under active workspace guardrails",
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
- when: "To evaluate guardrails and prepare an action for execution",
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, "&amp;")
2188
+ .replace(/</g, "&lt;")
2189
+ .replace(/>/g, "&gt;")
2190
+ .replace(/"/g, "&quot;");
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
+ }