@muxoai/sdk 1.3.0 → 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 (72) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +19 -4
  3. package/.codex/config.toml +9 -2
  4. package/ARCHITECTURE.md +16 -6
  5. package/README.md +69 -24
  6. package/dist/clerk.d.ts +35 -0
  7. package/dist/clerk.js +201 -0
  8. package/dist/cli.js +1589 -103
  9. package/dist/composio-catalog.d.ts +234 -0
  10. package/dist/composio-catalog.js +1894 -0
  11. package/dist/config.d.ts +16 -0
  12. package/dist/config.js +27 -1
  13. package/dist/durable-workflow.d.ts +45 -0
  14. package/dist/durable-workflow.js +90 -0
  15. package/dist/engine.d.ts +315 -6
  16. package/dist/engine.js +1778 -94
  17. package/dist/mcp-schema.d.ts +286 -0
  18. package/dist/mcp-schema.js +2559 -0
  19. package/dist/mcp.d.ts +2 -25
  20. package/dist/mcp.js +949 -507
  21. package/dist/memory-graph.d.ts +21 -0
  22. package/dist/memory-graph.js +173 -0
  23. package/dist/nl.js +214 -35
  24. package/dist/oauth.d.ts +32 -0
  25. package/dist/oauth.js +134 -0
  26. package/dist/policy.d.ts +190 -0
  27. package/dist/policy.js +1597 -0
  28. package/dist/providers/composio-instant.d.ts +4 -0
  29. package/dist/providers/composio-instant.js +890 -535
  30. package/dist/providers/composio-sdk.d.ts +126 -5
  31. package/dist/providers/composio-sdk.js +902 -79
  32. package/dist/providers/microsoft-governance.d.ts +89 -3
  33. package/dist/providers/microsoft-governance.js +673 -26
  34. package/dist/providers/stripe-billing.d.ts +42 -0
  35. package/dist/providers/stripe-billing.js +244 -5
  36. package/dist/providers/stripe-projects.d.ts +459 -4
  37. package/dist/providers/stripe-projects.js +2674 -122
  38. package/dist/providers/supermemory.d.ts +102 -6
  39. package/dist/providers/supermemory.js +609 -55
  40. package/dist/provisioning-managed.d.ts +50 -0
  41. package/dist/provisioning-managed.js +155 -0
  42. package/dist/sdk.d.ts +705 -7
  43. package/dist/sdk.js +488 -10
  44. package/dist/server.js +2646 -348
  45. package/dist/store.d.ts +138 -6
  46. package/dist/store.js +932 -58
  47. package/dist/types.d.ts +312 -4
  48. package/dist/ui/console-html.d.ts +62 -0
  49. package/dist/ui/console-html.js +3742 -2228
  50. package/dist/ui/design-tokens.d.ts +177 -0
  51. package/dist/ui/design-tokens.js +210 -0
  52. package/dist/version.d.ts +1 -0
  53. package/dist/version.js +1 -0
  54. package/package.json +5 -4
  55. package/plugins/muxo/.app.json +1 -1
  56. package/plugins/muxo/.claude-plugin/plugin.json +3 -3
  57. package/plugins/muxo/.codex-plugin/plugin.json +19 -10
  58. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  59. package/plugins/muxo/.mcp.json +1 -33
  60. package/plugins/muxo/assets/logo.png +0 -0
  61. package/plugins/muxo/hooks/session-start.sh +16 -4
  62. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  63. package/plugins/muxo/mcp.json +0 -10
  64. package/plugins/muxo/plugin.json +65 -8
  65. package/plugins/muxo/skills/muxo/SKILL.md +142 -14
  66. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  67. package/plugins/muxo/skills/muxo/references/cli.md +9 -3
  68. package/plugins/muxo/skills/muxo/references/mcp.md +22 -10
  69. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  70. package/plugins/muxo/skills/setup/SKILL.md +17 -3
  71. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
  72. package/src/catalogues/stripe-projects-catalog.json +140 -0
@@ -0,0 +1,2559 @@
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
+ }
316
+ export const MCP_TOOLS_OAUTH_SCOPE = "mcp:tools";
317
+ /** Workspace-bound tools require an OAuth bearer token (or workspace API key). */
318
+ export const DEFAULT_TOOL_SECURITY_SCHEMES = [
319
+ { type: "oauth2", scopes: [MCP_TOOLS_OAUTH_SCOPE] },
320
+ ];
321
+ /** Catalog discovery tools work anonymously and also accept OAuth. */
322
+ export const PUBLIC_TOOL_SECURITY_SCHEMES = [
323
+ { type: "noauth" },
324
+ { type: "oauth2", scopes: [MCP_TOOLS_OAUTH_SCOPE] },
325
+ ];
326
+ export const PUBLIC_MCP_TOOL_NAMES = new Set([
327
+ "search_tools",
328
+ "inspect_tool",
329
+ "search",
330
+ "fetch",
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
+ }
483
+ /** Stable per-workspace agent identity returned at agent signup on both runtimes. */
484
+ export function workspaceAgentDid(workspaceId) {
485
+ return `did:agent:muxo:${workspaceId}`;
486
+ }
487
+ /** JSON-RPC methods that anonymous MCP clients may call for discovery. */
488
+ export const PUBLIC_MCP_METHODS = new Set([
489
+ "server/discover",
490
+ "initialize",
491
+ "ping",
492
+ "tools/list",
493
+ "resources/list",
494
+ "resources/read",
495
+ "resources/templates/list",
496
+ "prompts/list",
497
+ "completion/complete",
498
+ "subscriptions/listen",
499
+ "skills/list",
500
+ "skills/get",
501
+ ]);
502
+ /** True when a single JSON-RPC MCP request only needs public discovery access. */
503
+ export function isPublicMcpRequest(rpc) {
504
+ if (!rpc || typeof rpc !== "object" || Array.isArray(rpc))
505
+ return false;
506
+ const message = rpc;
507
+ const method = String(message.method || "");
508
+ if (method.startsWith("notifications/") || PUBLIC_MCP_METHODS.has(method))
509
+ return true;
510
+ if (method === "tools/call") {
511
+ const params = (message.params || {});
512
+ return PUBLIC_MCP_TOOL_NAMES.has(String(params.name || ""));
513
+ }
514
+ return false;
515
+ }
516
+ export function securitySchemesForTool(name) {
517
+ return PUBLIC_MCP_TOOL_NAMES.has(name)
518
+ ? PUBLIC_TOOL_SECURITY_SCHEMES
519
+ : DEFAULT_TOOL_SECURITY_SCHEMES;
520
+ }
521
+ /** Maximum in-turn polling window for wait_for_connection and get_plan(wait_for_approval). */
522
+ export const MAX_MCP_WAIT_SECONDS = 120;
523
+ export const DEFAULT_MCP_WAIT_SECONDS = 15;
524
+ export function clampWaitSeconds(raw, fallback = DEFAULT_MCP_WAIT_SECONDS, max = MAX_MCP_WAIT_SECONDS) {
525
+ const n = raw === undefined || raw === null || raw === "" ? fallback : Number(raw);
526
+ if (!Number.isFinite(n))
527
+ return Math.min(fallback, max);
528
+ return Math.max(0, Math.min(max, n));
529
+ }
530
+ /** Matches explicit catalog slugs (e.g. 'app.github.create_issue') as opposed to natural-language requests. */
531
+ export function looksLikeToolSlug(raw) {
532
+ return /^[a-z0-9_-]+(\.[a-z0-9_-]+)+$/i.test(String(raw || "").trim());
533
+ }
534
+ /**
535
+ * Picks the live Composio search hit for a requested `app.<toolkit>.<action>` slug: an exact
536
+ * slug/upstream match, else a same-toolkit tool whose slug contains every action word. Returns
537
+ * null rather than an unrelated top search result.
538
+ */
539
+ export function matchAppToolSlug(requestedSlug, candidates) {
540
+ const clean = String(requestedSlug || "").trim();
541
+ const parts = clean.split(".");
542
+ if (parts[0] !== "app" || parts.length < 3)
543
+ return null;
544
+ const toolkit = parts[1].toLowerCase();
545
+ const action = parts.slice(2).join("_").toLowerCase();
546
+ const upstream = `${toolkit}_${action}`.toUpperCase();
547
+ const exact = candidates.find((c) => c.slug === clean || c.upstreamSlug.toUpperCase() === upstream);
548
+ if (exact)
549
+ return exact;
550
+ const words = action.split(/[_\s]+/).filter(Boolean);
551
+ if (words.length === 0)
552
+ return null;
553
+ return (candidates.find((c) => c.providerSlug.toLowerCase() === toolkit &&
554
+ words.every((w) => c.slug.toLowerCase().includes(w))) || null);
555
+ }
556
+ export const DEFAULT_MCP_SKILL_NAME = "muxo";
557
+ /**
558
+ * SEP-2640 `skills/get` lookup. A `uri` takes precedence over `name`, and a miss returns
559
+ * `skill: undefined` so callers answer with JSON-RPC -32602 rather than a default skill.
560
+ */
561
+ export function findMcpSkill(skills, params) {
562
+ const requestedUri = String(params.uri || "").trim();
563
+ if (requestedUri) {
564
+ return { skill: skills.find((s) => s.uri === requestedUri), requested: requestedUri };
565
+ }
566
+ const requestedName = String(params.name || "").trim() || DEFAULT_MCP_SKILL_NAME;
567
+ return { skill: skills.find((s) => s.name === requestedName), requested: requestedName };
568
+ }
569
+ export const APPROVED_PLAN_STATUSES = new Set([
570
+ "approved",
571
+ "auto_approved",
572
+ "executed",
573
+ ]);
574
+ export const GET_RUN_CONTEXT_ACTIONS = [
575
+ "inspect",
576
+ "remember",
577
+ "recall",
578
+ "forget",
579
+ "memory_graph",
580
+ "set_guardrail",
581
+ "reset_guardrails",
582
+ "check_policy",
583
+ "kill_switch",
584
+ "governance_status",
585
+ "scan_context",
586
+ "list_workspaces",
587
+ "switch_workspace",
588
+ "onboard_team",
589
+ ];
590
+ /** Error carrying a structured MCP/REST payload (merged into structuredContent / JSON body). */
591
+ export class MuxoToolError extends Error {
592
+ code;
593
+ statusCode;
594
+ payload;
595
+ constructor(message, options) {
596
+ super(message);
597
+ this.name = "MuxoToolError";
598
+ this.code = options.code;
599
+ this.statusCode = options.statusCode ?? 400;
600
+ this.payload = { code: options.code, ...(options.payload || {}) };
601
+ }
602
+ }
603
+ export function unknownToolError(slug) {
604
+ const clean = String(slug || "").trim();
605
+ const keyword = clean.split(".").filter(Boolean).slice(-1)[0] || clean;
606
+ const message = clean
607
+ ? `Tool not found: '${clean}' is not in the Muxo catalog. Call search_tools with a keyword (e.g. query "${keyword}") to find a valid tool slug, then call inspect_tool or plan_action with that slug.`
608
+ : "tool_slug is required. Call search_tools to find a valid tool slug, then call inspect_tool or plan_action with that slug.";
609
+ return new MuxoToolError(message, {
610
+ code: "tool_not_found",
611
+ statusCode: 404,
612
+ payload: {
613
+ toolSlug: clean || null,
614
+ recovery: {
615
+ summary: "Search the catalog for a valid slug, then retry with it.",
616
+ nextToolCalls: [
617
+ {
618
+ tool: "search_tools",
619
+ input: { query: keyword || "" },
620
+ when: "To find the correct tool slug",
621
+ },
622
+ ],
623
+ },
624
+ },
625
+ });
626
+ }
627
+ export function buildApprovalUrl(baseUrl, planId) {
628
+ return `${baseUrl.replace(/\/$/, "")}/approve/${encodeURIComponent(planId)}`;
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
+ }
675
+ /** Structured response returned when execute_<risk> is called on a plan still awaiting approval. */
676
+ export function buildApprovalRequiredPayload(baseUrl, plan) {
677
+ const approvalUrl = buildApprovalUrl(baseUrl, plan.id);
678
+ return {
679
+ approvalRequired: true,
680
+ planId: plan.id,
681
+ toolSlug: plan.toolSlug,
682
+ riskLevel: plan.riskLevel,
683
+ approvalStatus: plan.approvalStatus,
684
+ approvalUrl,
685
+ humanHandoff: buildApprovalHumanHandoff(baseUrl, plan),
686
+ polling: {
687
+ tool: "get_plan",
688
+ input: { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 30 },
689
+ restUrl: `${baseUrl.replace(/\/$/, "")}/api/v1/plans/${encodeURIComponent(plan.id)}/wait?timeout_seconds=30`,
690
+ },
691
+ llmGuidance: {
692
+ summary: `Plan ${plan.id} is awaiting human approval. Share ${approvalUrl} with the user, poll get_plan(wait_for_approval: true), then call execute_${plan.riskLevel} once approved.`,
693
+ nextToolCalls: [
694
+ {
695
+ tool: "get_plan",
696
+ input: { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 30 },
697
+ when: `After sharing ${approvalUrl} with the user, to wait in-turn until they click Approve`,
698
+ },
699
+ {
700
+ tool: `execute_${plan.riskLevel}`,
701
+ input: { plan_id: plan.id },
702
+ when: "Only after get_plan reports approved: true",
703
+ },
704
+ ],
705
+ },
706
+ };
707
+ }
708
+ export function approvalRequiredError(baseUrl, plan) {
709
+ const approvalUrl = buildApprovalUrl(baseUrl, plan.id);
710
+ return new MuxoToolError(`Plan '${plan.id}' (${plan.riskLevel}) is awaiting human approval. Open ${approvalUrl} to approve it (or run 'muxo approve ${plan.id}'), then poll get_plan with wait_for_approval: true before calling execute_${plan.riskLevel}.`, {
711
+ code: "approval_required",
712
+ statusCode: 409,
713
+ payload: buildApprovalRequiredPayload(baseUrl, plan),
714
+ });
715
+ }
716
+ export function errorPayloadFrom(err, fallback = "Tool execution failed") {
717
+ const message = String(err?.message || err || fallback);
718
+ return err instanceof MuxoToolError
719
+ ? { ok: false, error: message, ...err.payload }
720
+ : { ok: false, error: message };
721
+ }
722
+ /**
723
+ * MCP onboard_team never mints credentials in-band; it returns the human handoff
724
+ * (Console / CLI / REST) that issues a Team API key so secrets stay out of agent transcripts.
725
+ */
726
+ export function buildOnboardTeamGuide(input) {
727
+ const base = input.baseUrl.replace(/\/$/, "");
728
+ const teamName = (input.teamName || "").trim() || "Engineering Team";
729
+ return {
730
+ ok: true,
731
+ action: "onboard_team",
732
+ workspaceId: input.workspaceId,
733
+ workspaceName: input.workspaceName,
734
+ teamName,
735
+ consoleUrl: `${base}/keys`,
736
+ onboardingEndpoint: {
737
+ method: "POST",
738
+ url: `${base}/api/v1/workspaces/onboard-team`,
739
+ body: { teamName, mode: "shared_workspace", role: "operator" },
740
+ },
741
+ cli: {
742
+ onboard: `muxo workspace onboard-team --team "${teamName}"`,
743
+ join: "muxo workspace join --api-key <team_key>",
744
+ },
745
+ instructions: "Generate a shared Team API Key in the Muxo Console (API Keys & Access), via `muxo workspace onboard-team`, or POST /api/v1/workspaces/onboard-team; teammates then join with `muxo workspace join --api-key <key>`.",
746
+ workspaces: input.workspaces || [],
747
+ };
748
+ }
749
+ /**
750
+ * Concise server instructions (<512 chars) per OpenAI Plugin MCP Server specification:
751
+ * https://developers.openai.com/plugins/build/mcp-server
752
+ */
753
+ export const MUXO_MCP_SERVER_INSTRUCTIONS = "Muxo governs SaaS actions (app.*), Instant tools (instant.*), Cloud Infrastructure (resource.*), Memory, and Guardrails. Workflow: 1) search_tools & inspect_tool. 2) plan_action to get a plan_*. 3) If authHandoff.required, show humanHandoff.url and call wait_for_connection. 4) If approvalStatus is pending_approval, send humanHandoff.url or call get_plan(wait_for_approval:true). 5) Execute via execute_<risk>. 6) Manage memory/guardrails via get_run_context.";
754
+ export const MUXO_MCP_TOOLS = [
755
+ {
756
+ name: "get_profile",
757
+ title: "Get Authenticated Profile",
758
+ description: "Get the authenticated actor profile, user ID, email, workspace ID, and subscription tier.",
759
+ inputSchema: {
760
+ type: "object",
761
+ properties: {},
762
+ additionalProperties: false,
763
+ },
764
+ outputSchema: {
765
+ $schema: "https://json-schema.org/draft/2020-12/schema",
766
+ type: "object",
767
+ properties: {
768
+ id: {
769
+ type: "string",
770
+ minLength: 1,
771
+ pattern: "\\S",
772
+ description: "Unique identifier for the user.",
773
+ },
774
+ name: {
775
+ type: "string",
776
+ description: "Full name of the user.",
777
+ },
778
+ email: {
779
+ type: "string",
780
+ description: "Email address of the user.",
781
+ },
782
+ nickname: {
783
+ type: "string",
784
+ description: "Handle or username of the user.",
785
+ },
786
+ workspaceId: {
787
+ type: "string",
788
+ description: "Active workspace ID.",
789
+ },
790
+ workspaceName: {
791
+ type: "string",
792
+ description: "Active workspace name.",
793
+ },
794
+ tier: {
795
+ type: "string",
796
+ description: "Workspace subscription tier.",
797
+ },
798
+ authType: {
799
+ type: "string",
800
+ description: "Authentication type (session or api_key).",
801
+ },
802
+ actorId: {
803
+ type: "string",
804
+ description: "Actor identifier.",
805
+ },
806
+ user: {
807
+ type: "object",
808
+ description: "User profile record.",
809
+ },
810
+ },
811
+ required: ["id"],
812
+ additionalProperties: false,
813
+ },
814
+ annotations: {
815
+ readOnlyHint: true,
816
+ destructiveHint: false,
817
+ openWorldHint: false,
818
+ },
819
+ _meta: mcpToolUiMeta("Loading Muxo workspace profile…", "Loaded workspace profile", { "openai/profile": true }),
820
+ },
821
+ {
822
+ name: "get_organization",
823
+ title: "Get Organization & Billing Summary",
824
+ description: "Get workspace governance policy, plain-English guardrails, monthly action usage, credit balance, workspace memberships, and active connection/resource counts.",
825
+ inputSchema: {
826
+ type: "object",
827
+ properties: {},
828
+ additionalProperties: false,
829
+ },
830
+ annotations: {
831
+ readOnlyHint: true,
832
+ destructiveHint: false,
833
+ openWorldHint: false,
834
+ },
835
+ _meta: mcpToolUiMeta("Loading organization & guardrails summary…", "Loaded organization summary"),
836
+ },
837
+ {
838
+ name: "search_tools",
839
+ title: "Search Unified Tool Catalog",
840
+ description: "Search the unified Muxo catalog across Connected Apps (app.*), Instant Tools (instant.*), Billing (billing.*), and Cloud Infrastructure (resource.*).",
841
+ inputSchema: {
842
+ type: "object",
843
+ properties: {
844
+ query: {
845
+ type: "string",
846
+ description: "Search keywords (e.g. 'postgres', 'github issue', 'web search', 'seo audit', 'stripe refund', 'redis')",
847
+ },
848
+ lane: {
849
+ type: "string",
850
+ enum: ["all", "composio_sdk", "composio_instant", "stripe_projects"],
851
+ default: "all",
852
+ },
853
+ risk_level: {
854
+ type: "string",
855
+ enum: [
856
+ "read",
857
+ "write",
858
+ "admin",
859
+ "financial",
860
+ "provisioning",
861
+ "destructive",
862
+ ],
863
+ },
864
+ limit: { type: "number", default: 20 },
865
+ },
866
+ additionalProperties: false,
867
+ },
868
+ annotations: {
869
+ readOnlyHint: true,
870
+ destructiveHint: false,
871
+ openWorldHint: true,
872
+ },
873
+ _meta: mcpToolUiMeta("Searching Muxo tool catalog…", "Found matching tools"),
874
+ },
875
+ {
876
+ name: "inspect_tool",
877
+ title: "Inspect Tool Schema & Risk",
878
+ description: "Inspect a tool's full input JSON schema, risk classification, provider lane, LLM context URL, and credit cost before planning.",
879
+ inputSchema: {
880
+ type: "object",
881
+ required: ["tool_slug"],
882
+ properties: {
883
+ tool_slug: {
884
+ type: "string",
885
+ description: "Canonical Muxo tool slug (e.g. 'instant.seo.audit', 'app.gmail.send_email', 'billing.stripe.create_refund', or 'resource.turso.database')",
886
+ },
887
+ },
888
+ additionalProperties: false,
889
+ },
890
+ annotations: {
891
+ readOnlyHint: true,
892
+ destructiveHint: false,
893
+ openWorldHint: false,
894
+ },
895
+ _meta: mcpToolUiMeta("Inspecting tool schema & risk lane…", "Inspected tool schema"),
896
+ },
897
+ {
898
+ name: "connection_status",
899
+ title: "Check Connection Status",
900
+ description: "Check connection status across Connected Apps, Instant Tools, and linked Cloud Infrastructure providers.",
901
+ inputSchema: {
902
+ type: "object",
903
+ properties: {
904
+ provider: {
905
+ type: "string",
906
+ description: "Optional provider filter (e.g. 'github', 'gmail', 'slack', 'neon', 'turso')",
907
+ },
908
+ sync_live: {
909
+ type: "boolean",
910
+ default: true,
911
+ description: "Whether to sync live connection status from upstream providers",
912
+ },
913
+ },
914
+ additionalProperties: false,
915
+ },
916
+ annotations: {
917
+ readOnlyHint: true,
918
+ destructiveHint: false,
919
+ openWorldHint: true,
920
+ },
921
+ _meta: mcpToolUiMeta("Checking provider connection status…", "Checked connection status"),
922
+ },
923
+ {
924
+ name: "start_connection",
925
+ title: "Start OAuth Connection Handoff",
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.",
927
+ inputSchema: {
928
+ type: "object",
929
+ required: ["provider"],
930
+ properties: {
931
+ provider: {
932
+ type: "string",
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.",
934
+ },
935
+ lane: {
936
+ type: "string",
937
+ enum: ["composio_sdk", "stripe_projects"],
938
+ description: "Provider lane (Cloud Infrastructure providers automatically route to stripe_projects with zero OAuth; SaaS apps use composio_sdk).",
939
+ },
940
+ callback_url: {
941
+ type: "string",
942
+ description: "Optional OAuth callback URL",
943
+ },
944
+ },
945
+ additionalProperties: false,
946
+ },
947
+ annotations: {
948
+ readOnlyHint: false,
949
+ destructiveHint: false,
950
+ openWorldHint: true,
951
+ },
952
+ _meta: mcpToolUiMeta("Preparing OAuth connection handoff…", "Prepared OAuth connection link"),
953
+ },
954
+ {
955
+ name: "wait_for_connection",
956
+ title: "Wait for OAuth Connection Handoff",
957
+ description: "Poll and wait for an external user's pending OAuth connection handoff to become active before resuming a planned operation.",
958
+ inputSchema: {
959
+ type: "object",
960
+ required: ["provider"],
961
+ properties: {
962
+ provider: {
963
+ type: "string",
964
+ description: "Provider or toolkit slug (e.g. 'github', 'gmail', 'slack', 'notion', 'linear')",
965
+ },
966
+ timeout_seconds: {
967
+ type: "number",
968
+ default: 15,
969
+ description: "Maximum seconds to poll for OAuth completion (0-120, default 15)",
970
+ },
971
+ },
972
+ additionalProperties: false,
973
+ },
974
+ annotations: {
975
+ readOnlyHint: true,
976
+ destructiveHint: false,
977
+ openWorldHint: true,
978
+ },
979
+ _meta: mcpToolUiMeta("Verifying OAuth connection status…", "Verified OAuth connection status"),
980
+ },
981
+ {
982
+ name: "plan_action",
983
+ title: "Create Governed Action Plan",
984
+ description: "Create a governed execution plan (plan_*) for any tool, billing operation, or infrastructure provisioning operation. Accepts either an exact tool_slug (e.g. 'instant.seo.audit', 'app.gmail.send_email', 'billing.stripe.create_refund', 'resource.turso.database') or a natural-language instruction. Evaluates workspace policy, conditional parameter guardrails, risk level, Workspace Memory recall, BYOK vs Muxo-funded mode, and credit balance.",
985
+ inputSchema: {
986
+ type: "object",
987
+ required: ["tool_slug"],
988
+ properties: {
989
+ tool_slug: {
990
+ type: "string",
991
+ description: "Muxo tool slug (e.g. 'instant.seo.audit', 'billing.stripe.create_refund') or natural-language request",
992
+ },
993
+ input: {
994
+ type: "object",
995
+ description: "Arguments matching the tool's inputSchema (auto-extracted when tool_slug is natural language)",
996
+ },
997
+ reason: {
998
+ type: "string",
999
+ description: "Audit reason explaining why this action is being planned",
1000
+ },
1001
+ },
1002
+ additionalProperties: false,
1003
+ },
1004
+ annotations: {
1005
+ readOnlyHint: false,
1006
+ destructiveHint: false,
1007
+ openWorldHint: false,
1008
+ },
1009
+ _meta: mcpToolUiMeta("Evaluating guardrails & creating action plan…", "Created governed action plan"),
1010
+ },
1011
+ {
1012
+ name: "get_plan",
1013
+ title: "Get Action Plan",
1014
+ description: "Inspect the status, risk level, governance decision record, and approval state of a planned action. Set wait_for_approval: true to poll in-turn while a human approves the plan at /approve/<plan_id>.",
1015
+ inputSchema: {
1016
+ type: "object",
1017
+ required: ["plan_id"],
1018
+ properties: {
1019
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1020
+ wait_for_approval: {
1021
+ type: "boolean",
1022
+ description: "If true, wait in-turn (up to timeout_seconds) until the plan transitions out of pending_approval.",
1023
+ },
1024
+ timeout_seconds: {
1025
+ type: "number",
1026
+ description: "Maximum seconds to wait when wait_for_approval is true (default 15, max 60).",
1027
+ },
1028
+ },
1029
+ additionalProperties: false,
1030
+ },
1031
+ annotations: {
1032
+ readOnlyHint: true,
1033
+ destructiveHint: false,
1034
+ openWorldHint: false,
1035
+ },
1036
+ _meta: mcpToolUiMeta("Checking action plan status…", "Loaded action plan status"),
1037
+ },
1038
+ {
1039
+ name: "get_run_context",
1040
+ title: "Get & Manage Workspace Context, Memory & Guardrails",
1041
+ description: "Get or manage the workspace runtime context, Supermemory v5 contextual brain (profile, hybrid search recall, remember/forget, bi-temporal knowledge graph), conditional Guardrails (natural language or structured parameter_rules compiled by your LLM harness), workspace switching, and active provisioned resources.",
1042
+ inputSchema: {
1043
+ type: "object",
1044
+ properties: {
1045
+ query: {
1046
+ type: "string",
1047
+ description: "Optional semantic search query to recall relevant workspace memories or filter the bi-temporal knowledge graph",
1048
+ },
1049
+ action: {
1050
+ type: "string",
1051
+ enum: [
1052
+ "inspect",
1053
+ "remember",
1054
+ "recall",
1055
+ "forget",
1056
+ "memory_graph",
1057
+ "set_guardrail",
1058
+ "reset_guardrails",
1059
+ "check_policy",
1060
+ "kill_switch",
1061
+ "governance_status",
1062
+ "scan_context",
1063
+ "list_workspaces",
1064
+ "switch_workspace",
1065
+ "onboard_team",
1066
+ ],
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'",
1068
+ },
1069
+ prompt: {
1070
+ type: "string",
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",
1084
+ },
1085
+ parameter_rules: {
1086
+ type: "array",
1087
+ description: "Optional structured conditional parameter guardrail rules compiled by the caller LLM harness when action='set_guardrail'. Each item: { toolPattern, parameter, operator, value, unit?, action, elseAction?, description }",
1088
+ items: {
1089
+ type: "object",
1090
+ properties: {
1091
+ id: { type: "string" },
1092
+ toolPattern: {
1093
+ type: "string",
1094
+ description: "Tool slug or wildcard pattern (e.g. 'app.gmail.send_email', '*refund*', '*email*')",
1095
+ },
1096
+ parameter: {
1097
+ type: "string",
1098
+ description: "Parameter name or comma-separated aliases (e.g. 'to', 'amount', 'channel')",
1099
+ },
1100
+ operator: {
1101
+ type: "string",
1102
+ enum: [
1103
+ "equals",
1104
+ "must_equal",
1105
+ "not_equals",
1106
+ "contains",
1107
+ "not_contains",
1108
+ "starts_with",
1109
+ "must_start_with",
1110
+ "ends_with",
1111
+ "must_end_with",
1112
+ "domain_in",
1113
+ "domain_not_in",
1114
+ "max_number",
1115
+ "min_number",
1116
+ "gt",
1117
+ "gte",
1118
+ "lt",
1119
+ "lte",
1120
+ "in",
1121
+ "not_in",
1122
+ "regex",
1123
+ ],
1124
+ },
1125
+ value: {
1126
+ description: "Target value (string, number, or array of strings)",
1127
+ },
1128
+ unit: {
1129
+ type: "string",
1130
+ enum: ["usd", "cents", "credits", "raw"],
1131
+ },
1132
+ action: {
1133
+ type: "string",
1134
+ enum: ["block", "require_approval", "allow", "auto_approve"],
1135
+ },
1136
+ elseAction: {
1137
+ type: "string",
1138
+ enum: ["block", "require_approval", "allow", "auto_approve"],
1139
+ },
1140
+ description: { type: "string" },
1141
+ },
1142
+ },
1143
+ },
1144
+ workspace_id: {
1145
+ type: "string",
1146
+ description: "Target workspace ID when action='switch_workspace'",
1147
+ },
1148
+ team_name: {
1149
+ type: "string",
1150
+ description: "Team name when action='onboard_team'",
1151
+ },
1152
+ title: {
1153
+ type: "string",
1154
+ description: "Optional memory title when saving a fact via action='remember'",
1155
+ },
1156
+ content: {
1157
+ type: "string",
1158
+ description: "Optional memory content or text to scan",
1159
+ },
1160
+ include_graph: {
1161
+ type: "boolean",
1162
+ description: "If true, attach the Bi-Temporal Knowledge Graph (nodes, activeEdges, historicalEdges, mermaid) to the context response",
1163
+ },
1164
+ plan_id: {
1165
+ type: "string",
1166
+ description: "Optional plan ID to inspect context for",
1167
+ },
1168
+ run_id: {
1169
+ type: "string",
1170
+ description: "Optional run ID to inspect context for",
1171
+ },
1172
+ },
1173
+ additionalProperties: false,
1174
+ },
1175
+ annotations: {
1176
+ readOnlyHint: true,
1177
+ destructiveHint: false,
1178
+ openWorldHint: false,
1179
+ },
1180
+ _meta: mcpToolUiMeta("Loading workspace context, memory & guardrails…", "Loaded workspace context & guardrails"),
1181
+ },
1182
+ {
1183
+ name: "execute_read",
1184
+ title: "Execute Read Plan",
1185
+ description: "Execute an approved plan with riskLevel = 'read'.",
1186
+ inputSchema: {
1187
+ type: "object",
1188
+ required: ["plan_id"],
1189
+ properties: {
1190
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1191
+ },
1192
+ additionalProperties: false,
1193
+ },
1194
+ annotations: {
1195
+ readOnlyHint: true,
1196
+ destructiveHint: false,
1197
+ openWorldHint: true,
1198
+ },
1199
+ _meta: mcpToolUiMeta("Executing governed read operation…", "Completed read operation"),
1200
+ },
1201
+ {
1202
+ name: "execute_write",
1203
+ title: "Execute Write Plan",
1204
+ description: "Execute an approved plan with riskLevel = 'write'.",
1205
+ inputSchema: {
1206
+ type: "object",
1207
+ required: ["plan_id"],
1208
+ properties: {
1209
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1210
+ },
1211
+ additionalProperties: false,
1212
+ },
1213
+ annotations: {
1214
+ readOnlyHint: false,
1215
+ destructiveHint: false,
1216
+ openWorldHint: true,
1217
+ },
1218
+ _meta: mcpToolUiMeta("Executing governed write operation…", "Completed write operation"),
1219
+ },
1220
+ {
1221
+ name: "execute_admin",
1222
+ title: "Execute Admin Plan",
1223
+ description: "Execute an approved plan with riskLevel = 'admin'.",
1224
+ inputSchema: {
1225
+ type: "object",
1226
+ required: ["plan_id"],
1227
+ properties: {
1228
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1229
+ },
1230
+ additionalProperties: false,
1231
+ },
1232
+ annotations: {
1233
+ readOnlyHint: false,
1234
+ destructiveHint: false,
1235
+ openWorldHint: true,
1236
+ },
1237
+ _meta: mcpToolUiMeta("Executing governed admin operation…", "Completed admin operation"),
1238
+ },
1239
+ {
1240
+ name: "execute_financial",
1241
+ title: "Execute Financial Plan",
1242
+ description: "Execute an approved plan with riskLevel = 'financial'.",
1243
+ inputSchema: {
1244
+ type: "object",
1245
+ required: ["plan_id"],
1246
+ properties: {
1247
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1248
+ },
1249
+ additionalProperties: false,
1250
+ },
1251
+ annotations: {
1252
+ readOnlyHint: false,
1253
+ destructiveHint: false,
1254
+ openWorldHint: true,
1255
+ },
1256
+ _meta: mcpToolUiMeta("Executing governed financial operation…", "Completed financial operation"),
1257
+ },
1258
+ {
1259
+ name: "execute_provisioning",
1260
+ title: "Execute Infrastructure Provisioning Plan",
1261
+ description: "Execute an approved infrastructure provisioning plan with riskLevel = 'provisioning' via Stripe Projects.",
1262
+ inputSchema: {
1263
+ type: "object",
1264
+ required: ["plan_id"],
1265
+ properties: {
1266
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1267
+ },
1268
+ additionalProperties: false,
1269
+ },
1270
+ annotations: {
1271
+ readOnlyHint: false,
1272
+ destructiveHint: false,
1273
+ openWorldHint: true,
1274
+ },
1275
+ _meta: mcpToolUiMeta("Provisioning cloud infrastructure via Stripe Projects…", "Provisioned cloud resource"),
1276
+ },
1277
+ {
1278
+ name: "execute_destructive",
1279
+ title: "Execute Destructive Plan",
1280
+ description: "Execute an approved plan with riskLevel = 'destructive'.",
1281
+ inputSchema: {
1282
+ type: "object",
1283
+ required: ["plan_id"],
1284
+ properties: {
1285
+ plan_id: { type: "string", description: "Plan ID (plan_*)" },
1286
+ },
1287
+ additionalProperties: false,
1288
+ },
1289
+ annotations: {
1290
+ readOnlyHint: false,
1291
+ destructiveHint: true,
1292
+ openWorldHint: true,
1293
+ },
1294
+ _meta: mcpToolUiMeta("Executing approved destructive operation…", "Completed destructive operation"),
1295
+ },
1296
+ {
1297
+ name: "get_run",
1298
+ title: "Get Execution Run",
1299
+ description: "Get the execution status, output, duration, and metered cost of a run (run_*).",
1300
+ inputSchema: {
1301
+ type: "object",
1302
+ required: ["run_id"],
1303
+ properties: {
1304
+ run_id: { type: "string", description: "Run ID (run_*)" },
1305
+ },
1306
+ additionalProperties: false,
1307
+ },
1308
+ annotations: {
1309
+ readOnlyHint: true,
1310
+ destructiveHint: false,
1311
+ openWorldHint: false,
1312
+ },
1313
+ _meta: mcpToolUiMeta("Loading execution run details…", "Loaded execution run"),
1314
+ },
1315
+ {
1316
+ name: "cancel_run",
1317
+ title: "Cancel Execution Run",
1318
+ description: "Cancel a run (status becomes 'cancelled') and issue a signed cancellation receipt. Completed runs cannot be cancelled.",
1319
+ inputSchema: {
1320
+ type: "object",
1321
+ required: ["run_id"],
1322
+ properties: {
1323
+ run_id: { type: "string", description: "Run ID (run_*)" },
1324
+ reason: {
1325
+ type: "string",
1326
+ description: "Optional audit reason recorded on the cancellation receipt",
1327
+ },
1328
+ },
1329
+ additionalProperties: false,
1330
+ },
1331
+ annotations: {
1332
+ readOnlyHint: false,
1333
+ destructiveHint: true,
1334
+ openWorldHint: false,
1335
+ },
1336
+ _meta: mcpToolUiMeta("Cancelling execution run…", "Cancelled execution run"),
1337
+ },
1338
+ {
1339
+ name: "get_receipt",
1340
+ title: "Verify Execution Receipt",
1341
+ description: "Fetch and cryptographically verify the HMAC-SHA256 + portable Ed25519 AgenticReceipt + Microsoft Governance DecisionRecord for a run (or pass receipt_id='latest' or 'chain' / verify_chain=true to audit the full workspace hash chain).",
1342
+ inputSchema: {
1343
+ type: "object",
1344
+ properties: {
1345
+ receipt_id: {
1346
+ type: "string",
1347
+ description: "Receipt ID (rcpt_*), Run ID (run_*), 'latest', or 'chain'. Provide receipt_id or run_id.",
1348
+ },
1349
+ run_id: {
1350
+ type: "string",
1351
+ description: "Run ID (run_*) whose receipt should be returned",
1352
+ },
1353
+ verify_chain: {
1354
+ type: "boolean",
1355
+ description: "If true, also verify the entire workspace receipt SHA-256 + Ed25519 hash chain from genesis to head",
1356
+ },
1357
+ },
1358
+ additionalProperties: false,
1359
+ },
1360
+ annotations: {
1361
+ readOnlyHint: true,
1362
+ destructiveHint: false,
1363
+ openWorldHint: false,
1364
+ },
1365
+ _meta: mcpToolUiMeta("Verifying Ed25519 & SHA-256 execution receipt…", "Verified cryptographic receipt"),
1366
+ },
1367
+ ];
1368
+ /**
1369
+ * OpenAI Company Knowledge standard `search` and `fetch` tool definitions
1370
+ * per https://developers.openai.com/plugins/build/mcp-server#company-knowledge-compatibility
1371
+ */
1372
+ export const OPENAI_COMPANY_KNOWLEDGE_TOOLS = [
1373
+ {
1374
+ name: "search",
1375
+ title: "Search Muxo Tools & Workspace Memory",
1376
+ description: "Search across Muxo tools, provisioned cloud resources, and Supermemory v5 workspace knowledge.",
1377
+ inputSchema: {
1378
+ type: "object",
1379
+ required: ["query"],
1380
+ properties: {
1381
+ query: { type: "string", description: "Search query string." },
1382
+ },
1383
+ additionalProperties: false,
1384
+ },
1385
+ annotations: {
1386
+ readOnlyHint: true,
1387
+ destructiveHint: false,
1388
+ openWorldHint: false,
1389
+ },
1390
+ },
1391
+ {
1392
+ name: "fetch",
1393
+ title: "Fetch Muxo Tool or Memory Record",
1394
+ description: "Retrieve complete details for a Muxo tool, resource, or workspace memory record by ID.",
1395
+ inputSchema: {
1396
+ type: "object",
1397
+ required: ["id"],
1398
+ properties: {
1399
+ id: {
1400
+ type: "string",
1401
+ description: "Identifier returned by search (e.g. 'tool:instant.seo.audit' or 'memory:mem_123').",
1402
+ },
1403
+ },
1404
+ additionalProperties: false,
1405
+ },
1406
+ annotations: {
1407
+ readOnlyHint: true,
1408
+ destructiveHint: false,
1409
+ openWorldHint: false,
1410
+ },
1411
+ },
1412
+ ];
1413
+ /**
1414
+ * Enriches any MCP tool output object with structured `llmGuidance` so agent harnesses
1415
+ * (Claude Code, Cursor, Codex, OpenAI MCP, Windsurf) always know the exact next tool call.
1416
+ */
1417
+ export function attachMcpHarnessGuidance(toolName, toolArgs, output) {
1418
+ if (!output || typeof output !== "object" || Array.isArray(output)) {
1419
+ return output;
1420
+ }
1421
+ const obj = output;
1422
+ if (obj.llmGuidance) {
1423
+ return output;
1424
+ }
1425
+ let guidance = null;
1426
+ switch (toolName) {
1427
+ case "get_profile":
1428
+ case "get_organization":
1429
+ guidance = {
1430
+ summary: "Workspace identity verified. Call get_run_context to load memory & guardrails, or search_tools to discover capabilities.",
1431
+ nextToolCalls: [
1432
+ {
1433
+ tool: "get_run_context",
1434
+ input: { action: "inspect" },
1435
+ when: "Before planning actions, to load active memory defaults and guardrails",
1436
+ },
1437
+ {
1438
+ tool: "search_tools",
1439
+ input: { query: "" },
1440
+ when: "To discover available Instant, Connected App, and Cloud Infrastructure tools",
1441
+ },
1442
+ ],
1443
+ };
1444
+ break;
1445
+ case "search_tools": {
1446
+ const firstSlug = Array.isArray(obj.tools) && obj.tools[0]?.slug
1447
+ ? String(obj.tools[0].slug)
1448
+ : "<tool_slug>";
1449
+ guidance = {
1450
+ summary: `Found ${Array.isArray(obj.tools) ? obj.tools.length : 0} matching tools. Call inspect_tool to check inputSchema or plan_action to create an execution plan.`,
1451
+ nextToolCalls: [
1452
+ {
1453
+ tool: "inspect_tool",
1454
+ input: { tool_slug: firstSlug },
1455
+ when: "To inspect required parameters and risk level before planning",
1456
+ },
1457
+ {
1458
+ tool: "plan_action",
1459
+ input: {
1460
+ tool_slug: firstSlug,
1461
+ input: {},
1462
+ reason: "Execute user request",
1463
+ },
1464
+ when: "When you have the required input parameters ready",
1465
+ },
1466
+ ],
1467
+ };
1468
+ break;
1469
+ }
1470
+ case "inspect_tool": {
1471
+ const slug = String(obj.slug || toolArgs.tool_slug || "<tool_slug>");
1472
+ guidance = {
1473
+ summary: `Inspected ${slug} (risk=${String(obj.riskLevel || "read")}). Call plan_action with input matching inputSchema.`,
1474
+ nextToolCalls: [
1475
+ {
1476
+ tool: "plan_action",
1477
+ input: {
1478
+ tool_slug: slug,
1479
+ input: {},
1480
+ reason: `Execute ${slug}`,
1481
+ },
1482
+ when: "Next, to evaluate workspace guardrails and obtain a plan_id",
1483
+ },
1484
+ ],
1485
+ };
1486
+ break;
1487
+ }
1488
+ case "start_connection": {
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
+ }
1519
+ guidance = {
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")}.`,
1523
+ nextToolCalls: [
1524
+ {
1525
+ tool: "wait_for_connection",
1526
+ input: { provider, timeout_seconds: 30 },
1527
+ when: "While the user completes the browser OAuth flow",
1528
+ },
1529
+ ],
1530
+ };
1531
+ break;
1532
+ }
1533
+ case "wait_for_connection": {
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
+ }
1563
+ guidance = obj.connected
1564
+ ? {
1565
+ summary: `${provider} is connected and active in Muxo. Proceed to plan_action or execute_<riskLevel>.`,
1566
+ nextToolCalls: [
1567
+ {
1568
+ tool: "plan_action",
1569
+ input: {
1570
+ tool_slug: `app.${provider}.`,
1571
+ input: {},
1572
+ reason: "Execute connected app action",
1573
+ },
1574
+ when: "Now that OAuth connection is active",
1575
+ },
1576
+ ],
1577
+ }
1578
+ : {
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.`,
1580
+ nextToolCalls: [
1581
+ {
1582
+ tool: "wait_for_connection",
1583
+ input: { provider, timeout_seconds: 30 },
1584
+ when: "After the user finishes authorizing in their browser",
1585
+ },
1586
+ ],
1587
+ };
1588
+ break;
1589
+ }
1590
+ case "get_plan": {
1591
+ const plan = obj.plan || obj;
1592
+ const planId = String(plan?.id || toolArgs.plan_id || "<plan_id>");
1593
+ const risk = String(plan?.riskLevel || "read");
1594
+ const status = String(obj.approvalStatus || plan?.approvalStatus || "auto_approved");
1595
+ guidance =
1596
+ status === "approved" || status === "auto_approved"
1597
+ ? {
1598
+ summary: `Plan ${planId} is ${status}. Call execute_${risk} to run it now.`,
1599
+ nextToolCalls: [
1600
+ {
1601
+ tool: `execute_${risk}`,
1602
+ input: { plan_id: planId },
1603
+ when: "Immediately; plan is approved",
1604
+ },
1605
+ ],
1606
+ }
1607
+ : {
1608
+ summary: `Plan ${planId} is ${status}. Share the approval link with the user and wait for approval before executing.`,
1609
+ nextToolCalls: [
1610
+ {
1611
+ tool: "get_plan",
1612
+ input: {
1613
+ plan_id: planId,
1614
+ wait_for_approval: true,
1615
+ timeout_seconds: 25,
1616
+ },
1617
+ when: "While waiting for the workspace owner to click Approve",
1618
+ },
1619
+ ],
1620
+ };
1621
+ break;
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
+ }
1697
+ case "execute_read":
1698
+ case "execute_write":
1699
+ case "execute_admin":
1700
+ case "execute_financial":
1701
+ case "execute_provisioning":
1702
+ case "execute_destructive": {
1703
+ const runId = String(obj.run?.id || obj.receipt?.runId || obj.receipt?.id || "<run_id>");
1704
+ guidance = {
1705
+ summary: `Execution completed (${String(obj.run?.status || "succeeded")}). Call get_receipt to verify the cryptographic Ed25519 receipt if needed.`,
1706
+ nextToolCalls: [
1707
+ {
1708
+ tool: "get_receipt",
1709
+ input: { receipt_id: runId },
1710
+ when: "To verify the signed receipt and governance decision record",
1711
+ },
1712
+ ],
1713
+ };
1714
+ break;
1715
+ }
1716
+ case "get_run_context":
1717
+ case "set_guardrail":
1718
+ case "reset_guardrails":
1719
+ case "check_policy":
1720
+ case "kill_switch":
1721
+ case "governance_status":
1722
+ case "configure_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",
1743
+ },
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
+ }
1756
+ break;
1757
+ }
1758
+ default:
1759
+ break;
1760
+ }
1761
+ if (guidance) {
1762
+ return {
1763
+ ...obj,
1764
+ llmGuidance: guidance,
1765
+ };
1766
+ }
1767
+ return output;
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
+ }