@muxoai/platform 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/README.md +1 -1
  4. package/dist/cli.js +6 -3
  5. package/dist/composio-catalog.d.ts +177 -1
  6. package/dist/composio-catalog.js +1427 -7
  7. package/dist/config.d.ts +6 -0
  8. package/dist/config.js +11 -1
  9. package/dist/engine.d.ts +18 -1
  10. package/dist/engine.js +259 -35
  11. package/dist/mcp-schema.d.ts +162 -1
  12. package/dist/mcp-schema.js +1523 -40
  13. package/dist/mcp.d.ts +2 -2
  14. package/dist/mcp.js +412 -23
  15. package/dist/policy.d.ts +108 -1
  16. package/dist/policy.js +406 -1
  17. package/dist/providers/composio-instant.js +38 -3
  18. package/dist/providers/composio-sdk.d.ts +59 -2
  19. package/dist/providers/composio-sdk.js +273 -59
  20. package/dist/providers/stripe-projects.d.ts +4 -0
  21. package/dist/providers/stripe-projects.js +548 -126
  22. package/dist/server.js +531 -61
  23. package/dist/store.js +2 -0
  24. package/dist/types.d.ts +15 -0
  25. package/dist/ui/console-html.d.ts +31 -1
  26. package/dist/ui/console-html.js +922 -368
  27. package/dist/ui/design-tokens.js +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/package.json +3 -2
  31. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  32. package/plugins/muxo/.codex-plugin/plugin.json +15 -8
  33. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  34. package/plugins/muxo/hooks/session-start.sh +15 -3
  35. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  36. package/plugins/muxo/mcp.json +0 -10
  37. package/plugins/muxo/plugin.json +4 -4
  38. package/plugins/muxo/skills/muxo/SKILL.md +25 -16
  39. package/plugins/muxo/skills/muxo/references/cli.md +5 -1
  40. package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
  41. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  42. package/plugins/muxo/skills/setup/SKILL.md +1 -1
  43. package/src/catalogues/stripe-projects-catalog.json +140 -0
package/dist/server.js CHANGED
@@ -5,13 +5,14 @@ import path from "node:path";
5
5
  import { getConfig, ROOT_DIR } from "./config.js";
6
6
  import { MuxoEngine } from "./engine.js";
7
7
  import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.js";
8
- import { APPROVED_PLAN_STATUSES, MuxoToolError, clampWaitSeconds, errorPayloadFrom, isPublicMcpRequest, workspaceAgentDid, } from "./mcp-schema.js";
8
+ import { APPROVED_PLAN_STATUSES, LATEST_MCP_PROTOCOL_VERSION, MuxoToolError, SUPPORTED_MCP_PROTOCOL_VERSIONS, clampWaitSeconds, errorPayloadFrom, isPublicMcpRequest, validateMcpHttpHeadersAndMeta, workspaceAgentDid, } from "./mcp-schema.js";
9
9
  import { MuxoStore } from "./store.js";
10
10
  import { managedProviderConnectionList, managedProviderConnectionRequest, managedProviderConnectionUnlink, managedResource, managedResourceAction, } from "./provisioning-managed.js";
11
11
  import { DEFAULT_WORKSPACE_POLICY, describePolicyInPlainEnglish, sanitizePolicyPatch, } from "./policy.js";
12
12
  import { parseNaturalLanguageMemoryInstruction } from "./providers/supermemory.js";
13
13
  import { MUXO_VERSION } from "./version.js";
14
- import { isConsolePath, renderApprovalPageHtml, renderConsoleHtml, renderLandingHtml, renderOAuthConsentHtml, } from "./ui/console-html.js";
14
+ import { fetchMuxoConnectLinkMetadata, isStripeProjectsExclusiveProvider, normalizeStripeProjectsProviderSlug, parseComposioWebhookEnvelope, submitMuxoConnectLink, verifyComposioWebhookSignature, wrapMuxoConnectUrl, } from "./composio-catalog.js";
15
+ import { isConsolePath, renderApprovalPageHtml, renderConsoleHtml, renderLandingHtml, renderOAuthCallbackPageHtml, renderOAuthConnectPageHtml, renderOAuthConsentHtml, } from "./ui/console-html.js";
15
16
  import { fetchClerkProfile, looksLikeJwt, readClerkConfig, verifyClerkSessionToken, } from "./clerk.js";
16
17
  import { DEFAULT_OAUTH_SCOPE, OAUTH_ACCESS_TOKEN_TTL_SECONDS, OAUTH_CODE_TTL_MS, oauthConnectionKeyName, oauthRedirectOrigin, oauthSecretToken, parseAuthorizeRequest, parseOAuthFormBody, verifyPkce, } from "./oauth.js";
17
18
  function renderSkillMarkdown(baseUrl) {
@@ -109,7 +110,20 @@ Users and LLM harnesses can set safety rules in natural language or structured p
109
110
  - "Only post Slack messages to #ops-alerts or #deployments" → channel allowlist guardrail
110
111
  - "Never spend more than 25 credits per run" → workspace per-run credit cap
111
112
 
112
- Set or inspect them via MCP \`get_run_context({ action: "set_guardrail", prompt: "...", parameter_rules: [...] })\`, \`POST /api/v1/do\` (\`{ "prompt": "..." }\`), \`POST /api/v1/guardrails\` (\`{ "prompt": "...", "parameterRules": [...] }\`), or list them with \`GET /api/v1/guardrails\`. Responses always include \`plainEnglishGuardrails\` so humans and agents can read the active rules.
113
+ Set or inspect them via MCP \`get_run_context({ action: "set_guardrail", prompt: "...", parameter_rules: [...] })\`, dry-run simulate a tool call before planning via \`get_run_context({ action: "check_policy", tool_slug: "...", input: { ... } })\`, reset guardrails via \`get_run_context({ action: "reset_guardrails" })\`, toggle the emergency kill switch via \`get_run_context({ action: "kill_switch", enabled: true, reason: "..." })\`, or scan untrusted inputs for OWASP ASI-06 prompt injection via \`get_run_context({ action: "scan_context", prompt: "..." })\`. Equivalent REST routes: \`POST /api/v1/do\`, \`GET/POST /api/v1/guardrails\`, \`POST /api/v1/guardrails/check\`, and \`POST /api/v1/guardrails/reset\`. Responses always include \`plainEnglishGuardrails\` and a structured \`llmPolicyBrief\` (\`riskLaneMatrix\`, \`parameterRules[].howToComply\`, \`llmSystemDirective\`, and \`agentControlCheatsheet\`).
114
+
115
+ ## MCP Resources & Prompts for AI Harnesses
116
+
117
+ - **MCP Resources (\`resources/list\` & \`resources/read\`)**:
118
+ - \`muxo://policy/brief\` — Structured \`llmPolicyBrief\` (risk-lane approval matrix, parameter compliance rules, learned defaults, and agent control cheatsheet)
119
+ - \`muxo://governance/policy\` — Active guardrails, kill-switch status, trust score, and \`llmPolicyBrief\`
120
+ - \`muxo://context\` — Live workspace context, active guardrails, provisioned resources, and \`llmSystemPrompt\`
121
+ - \`muxo://memory/graph\` — Active Supermemory defaults and bi-temporal knowledge graph facts
122
+ - **MCP Prompts (\`prompts/list\` & \`prompts/get\`)**:
123
+ - \`context\` — Inject workspace memory, active defaults, and guardrail summary
124
+ - \`policy_guardrails\` — Inject the active \`llmSystemDirective\`, risk-lane matrix, and optional dry-run policy simulation for \`tool_slug\`
125
+ - \`governed_execution\` — Step-by-step Plan → Approve → Execute → Receipt system instructions
126
+ - \`security_audit\` — OWASP Agentic Top 10 security and receipt audit workflow
113
127
 
114
128
  ## Active Supermemory (teach Muxo once, auto-fill forever)
115
129
 
@@ -134,8 +148,8 @@ function sendJson(res, status, payload, extraHeaders) {
134
148
  res.writeHead(status, {
135
149
  "Content-Type": "application/json; charset=utf-8",
136
150
  "Access-Control-Allow-Origin": "*",
137
- "Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id",
138
- "Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
151
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id, Mcp-Session-Id, mcp-session-id, MCP-Protocol-Version, mcp-protocol-version, Mcp-Method, mcp-method, Mcp-Name, mcp-name",
152
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version, WWW-Authenticate",
139
153
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
140
154
  ...(extraHeaders || {}),
141
155
  });
@@ -320,7 +334,8 @@ export function createMuxoServer(customConfig) {
320
334
  if (req.method === "OPTIONS") {
321
335
  res.writeHead(204, {
322
336
  "Access-Control-Allow-Origin": "*",
323
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id",
337
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id, Mcp-Session-Id, mcp-session-id, MCP-Protocol-Version, mcp-protocol-version, Mcp-Method, mcp-method, Mcp-Name, mcp-name",
338
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version, WWW-Authenticate",
324
339
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
325
340
  });
326
341
  res.end();
@@ -746,8 +761,14 @@ export function createMuxoServer(customConfig) {
746
761
  "/api/v1/governance/kill-switch": { post: { summary: "Pause or resume workspace execution via kill switch" } },
747
762
  "/api/v1/governance/scan-context": { post: { summary: "Scan untrusted text for prompt injection before storage or use" } },
748
763
  "/api/v1/guardrails": {
749
- get: { summary: "List active plain-English guardrails, policy, and learned memory defaults" },
750
- post: { summary: "Set or reset guardrails in plain English (e.g. 'never send emails')" },
764
+ get: { summary: "List active plain-English guardrails, llmPolicyBrief, policy, and learned memory defaults" },
765
+ post: { summary: "Set or reset guardrails in plain English or structured parameterRules" },
766
+ },
767
+ "/api/v1/guardrails/check": {
768
+ post: { summary: "Dry-run evaluate a tool call and arguments against active workspace policy, conditional parameter rules, and memory defaults" },
769
+ },
770
+ "/api/v1/guardrails/reset": {
771
+ post: { summary: "Reset workspace guardrails and parameter rules to Safe Default" },
751
772
  },
752
773
  "/approve/{planId}": { get: { summary: "One-click human approval handoff page for a pending plan" } },
753
774
  "/api/v1/context": {
@@ -806,10 +827,10 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
806
827
  }
807
828
  if (pathname === "/llms.txt" && req.method === "GET") {
808
829
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
809
- res.end(`# Muxo\n> Unified agent execution across Connected Apps, Instant Tools, Cloud Infrastructure, Workspace Memory, and Safety Guardrails.\n\n- Skill Document: ${config.baseUrl}/skill.md\n- Auth Guide: ${config.baseUrl}/auth.md\n- OpenAPI 3.1 Spec: ${config.baseUrl}/api/openapi.json\n- MCP Endpoint: ${config.baseUrl}/mcp\n- OAuth Callback Handoff: ${config.baseUrl}/oauth/callback\n- Plugin Marketplace (Codex/OpenAI): ${config.baseUrl}/.agents/plugins/marketplace.json\n- Plugin Marketplace (Claude/Droid): ${config.baseUrl}/.claude-plugin/marketplace.json\n- Console (app.muxo.ai): ${config.baseUrl}/\n- Landing & Provider Canvas (muxo.ai): ${config.baseUrl}/landing\n- Documentation (docs.muxo.ai): ${config.docsUrl}\n`);
830
+ res.end(`# Muxo\n> Unified agent execution across Connected Apps, Instant Tools, Cloud Infrastructure, Workspace Memory, and Safety Guardrails.\n\n- Skill Document: ${config.baseUrl}/skill.md\n- Auth Guide: ${config.baseUrl}/auth.md\n- OpenAPI 3.1 Spec: ${config.baseUrl}/api/openapi.json\n- MCP Endpoint: ${config.baseUrl}/mcp\n- OAuth Callback Handoff: ${config.baseUrl}/oauth/callback\n- Plugin Marketplace (Codex/OpenAI): ${config.baseUrl}/.agents/plugins/marketplace.json\n- Plugin Marketplace (Claude/Droid): ${config.baseUrl}/.claude-plugin/marketplace.json\n- Console (app.muxo.ai): ${config.baseUrl}/\n- Landing & Provider Canvas (muxo.ai): ${config.baseUrl}/landing\n- Documentation (docs.muxo.ai): ${config.docsUrl}\n\n## Agent-Native Policy, Context & Governance Interface\n- MCP Control Plane (\`get_run_context\`): \`action: "recall" | "remember" | "forget" | "set_guardrail" | "reset_guardrails" | "check_policy" | "governance_status" | "kill_switch" | "scan_context"\`\n- Strict /v1 Organization Brief: \`get_organization({ company_context: { required: false } })\` returns accepted workspace policy facts + learned defaults in \`company_context.packet\`\n- Dry-Run Policy Simulator: \`POST ${config.baseUrl}/api/v1/guardrails/check\` (\`{ "tool_slug": "...", "input": { ... } }\`) or MCP \`get_run_context({ action: "check_policy", tool_slug, input })\`\n- Guardrails & Policy Brief: \`GET/POST ${config.baseUrl}/api/v1/guardrails\`, \`POST ${config.baseUrl}/api/v1/guardrails/reset\`, \`GET ${config.baseUrl}/api/v1/governance\` (includes \`llmPolicyBrief\`)\n- MCP Resources: \`muxo://policy/brief\`, \`muxo://governance/policy\`, \`muxo://context\`, \`muxo://memory/graph\`\n- MCP Prompts: \`context\`, \`policy_guardrails\`, \`governed_execution\`, \`security_audit\`\n`);
810
831
  return;
811
832
  }
812
- // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude)
833
+ // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude + Cursor)
813
834
  const pluginJsonRoutes = {
814
835
  "/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
815
836
  "/.claude-plugin/marketplace.json": path.join(ROOT_DIR, ".claude-plugin", "marketplace.json"),
@@ -817,6 +838,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
817
838
  "/plugins/muxo/mcp.json": path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"),
818
839
  "/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
819
840
  "/plugins/muxo/.claude-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".claude-plugin", "plugin.json"),
841
+ "/plugins/muxo/.cursor-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".cursor-plugin", "plugin.json"),
820
842
  "/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
821
843
  "/plugins/muxo/.mcp.json": path.join(ROOT_DIR, "plugins", "muxo", ".mcp.json"),
822
844
  };
@@ -844,6 +866,10 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
844
866
  file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "setup", "SKILL.md"),
845
867
  contentType: "text/markdown; charset=utf-8",
846
868
  },
869
+ "/plugins/muxo/skills/muxo-activity-summary/SKILL.md": {
870
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo-activity-summary", "SKILL.md"),
871
+ contentType: "text/markdown; charset=utf-8",
872
+ },
847
873
  "/.codex/config.toml": {
848
874
  file: path.join(ROOT_DIR, ".codex", "config.toml"),
849
875
  contentType: "text/plain; charset=utf-8",
@@ -864,10 +890,15 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
864
890
  const portableManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"), "utf8"));
865
891
  const mcpConfig = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"), "utf8"));
866
892
  const codexManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"), "utf8"));
893
+ const cursorManifestPath = path.join(ROOT_DIR, "plugins", "muxo", ".cursor-plugin", "plugin.json");
894
+ const cursorManifest = fs.existsSync(cursorManifestPath)
895
+ ? JSON.parse(fs.readFileSync(cursorManifestPath, "utf8"))
896
+ : undefined;
867
897
  sendJson(res, 200, {
868
898
  ok: true,
869
899
  plugin: portableManifest,
870
900
  codexPlugin: codexManifest,
901
+ cursorPlugin: cursorManifest,
871
902
  mcp: mcpConfig,
872
903
  skills: getMuxoMcpSkills().map((s) => ({
873
904
  name: s.name,
@@ -884,11 +915,14 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
884
915
  portablePluginJson: `${config.baseUrl}/plugins/muxo/plugin.json`,
885
916
  portableMcpJson: `${config.baseUrl}/plugins/muxo/mcp.json`,
886
917
  onboardingSkill: `${config.baseUrl}/plugins/muxo/skills/setup/SKILL.md`,
918
+ activitySummarySkill: `${config.baseUrl}/plugins/muxo/skills/muxo-activity-summary/SKILL.md`,
887
919
  codexPluginJson: `${config.baseUrl}/plugins/muxo/.codex-plugin/plugin.json`,
920
+ cursorPluginJson: `${config.baseUrl}/plugins/muxo/.cursor-plugin/plugin.json`,
888
921
  codexConfigToml: `${config.baseUrl}/.codex/config.toml`,
889
922
  },
890
923
  installCommands: {
891
924
  codex: "codex plugin add muxo@muxo",
925
+ cursor: "/add-plugin muxo",
892
926
  cliSignup: "muxo login --signup --name my-agent",
893
927
  oauthHandoff: "muxo link github --wait",
894
928
  },
@@ -915,6 +949,35 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
915
949
  res.end(renderLandingHtml());
916
950
  return;
917
951
  }
952
+ // Public Stripe Projects Hosted Static Sites (GET /sites/:slug and GET /sites/:slug/*)
953
+ const siteMatch = pathname.match(/^\/sites\/([^/]+)(?:\/(.*))?$/);
954
+ if (siteMatch && req.method === "GET") {
955
+ const siteSlug = decodeURIComponent(siteMatch[1]).replace(/\/+$/, "");
956
+ const filePath = siteMatch[2] ? decodeURIComponent(siteMatch[2]) : undefined;
957
+ const htmlContent = engine.getHostedSiteHtml(siteSlug, filePath);
958
+ if (!htmlContent) {
959
+ sendJson(res, 404, {
960
+ ok: false,
961
+ code: "site_not_found",
962
+ error: `Hosted site '${siteSlug}' was not found.`,
963
+ });
964
+ return;
965
+ }
966
+ const contentType = filePath && filePath.endsWith(".css")
967
+ ? "text/css; charset=utf-8"
968
+ : filePath && filePath.endsWith(".js")
969
+ ? "application/javascript; charset=utf-8"
970
+ : filePath && filePath.endsWith(".json")
971
+ ? "application/json; charset=utf-8"
972
+ : "text/html; charset=utf-8";
973
+ res.writeHead(200, {
974
+ "Content-Type": contentType,
975
+ "Access-Control-Allow-Origin": "*",
976
+ "Cache-Control": "no-store",
977
+ });
978
+ res.end(htmlContent);
979
+ return;
980
+ }
918
981
  // Workspace claim link: read-only; accepting requires the invited person's verified sign-in.
919
982
  if (pathname.startsWith("/claim/") && req.method === "GET") {
920
983
  const claimSlug = decodeURIComponent(pathname.slice("/claim/".length));
@@ -1041,30 +1104,289 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1041
1104
  return;
1042
1105
  }
1043
1106
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1044
- res.end(`<!DOCTYPE html>
1045
- <html lang="en">
1046
- <head>
1047
- <meta charset="utf-8" />
1048
- <title>Muxo OAuth Handoff Complete</title>
1049
- <style>
1050
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #090a0f; color: #f4f4f6; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
1051
- .card { background: #12141f; border: 1px solid #26293d; border-radius: 14px; padding: 32px; max-width: 460px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
1052
- .badge { display: inline-block; background: rgba(16,185,129,0.15); color: #34d399; border: 1px solid rgba(16,185,129,0.35); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
1053
- h1 { font-size: 22px; margin: 0 0 10px; }
1054
- p { color: #a1a7bb; font-size: 14px; line-height: 1.5; margin: 0 0 20px; }
1055
- code { background: #1c1f30; padding: 3px 7px; border-radius: 6px; color: #e2e8f0; font-size: 12px; }
1056
- .btn { display: inline-block; background: #6366f1; color: #fff; text-decoration: none; padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 600; }
1057
- </style>
1058
- </head>
1059
- <body>
1060
- <div class="card">
1061
- <div class="badge">OAuth Handoff Complete (${handoff.status.toUpperCase()})</div>
1062
- <h1>${handoff.providerName} Connected to Muxo</h1>
1063
- <p>Workspace <code>${handoff.workspaceId}</code> is now linked${handoff.externalAccountId ? ` (<code>${handoff.externalAccountId}</code>)` : ""}. You can close this window and return to ChatGPT, Codex, Claude Code, Droid, or your terminal.</p>
1064
- <a class="btn" href="/?connected=${encodeURIComponent(handoff.providerSlug)}">Open Muxo Console</a>
1065
- </div>
1066
- </body>
1067
- </html>`);
1107
+ res.end(renderOAuthCallbackPageHtml({
1108
+ providerSlug: handoff.providerSlug,
1109
+ providerName: handoff.providerName,
1110
+ status: handoff.status,
1111
+ workspaceId: handoff.workspaceId,
1112
+ externalAccountId: handoff.externalAccountId,
1113
+ }));
1114
+ return;
1115
+ }
1116
+ // Public bare /oauth/connect or /connect redirector & zero-OAuth status handler
1117
+ if ((pathname === "/oauth/connect" || pathname === "/connect") && req.method === "GET") {
1118
+ const queryToken = (url.searchParams.get("token") ||
1119
+ url.searchParams.get("link_token") ||
1120
+ "").trim();
1121
+ const directRedirect = (url.searchParams.get("redirect_url") ||
1122
+ url.searchParams.get("url") ||
1123
+ url.searchParams.get("link") ||
1124
+ "").trim();
1125
+ const workspaceId = url.searchParams.get("workspace_id") ||
1126
+ url.searchParams.get("user_id") ||
1127
+ undefined;
1128
+ const rawProvider = url.searchParams.get("provider") ||
1129
+ url.searchParams.get("toolkit") ||
1130
+ "";
1131
+ const cleanProvider = normalizeStripeProjectsProviderSlug(rawProvider) ||
1132
+ rawProvider.trim().toLowerCase().replace(/^(app\.|resource\.|projects\.)/, "");
1133
+ const wantsJson = url.searchParams.get("format") === "json" ||
1134
+ String(req.headers.accept || "").includes("application/json");
1135
+ if (queryToken) {
1136
+ const targetUrl = queryToken.startsWith("s_")
1137
+ ? `https://backend.composio.dev/api/v3/s/${encodeURIComponent(queryToken.slice(2))}`
1138
+ : `https://connect.composio.dev/link/${encodeURIComponent(queryToken)}`;
1139
+ if (wantsJson) {
1140
+ sendJson(res, 200, { ok: true, status: "pending", redirectUrl: targetUrl });
1141
+ return;
1142
+ }
1143
+ res.writeHead(302, { Location: targetUrl, "Cache-Control": "no-store" });
1144
+ res.end();
1145
+ return;
1146
+ }
1147
+ if (directRedirect &&
1148
+ /^https:\/\/(?:connect|backend)\.composio\.dev\//i.test(directRedirect)) {
1149
+ if (wantsJson) {
1150
+ sendJson(res, 200, { ok: true, status: "pending", redirectUrl: directRedirect });
1151
+ return;
1152
+ }
1153
+ res.writeHead(302, { Location: directRedirect, "Cache-Control": "no-store" });
1154
+ res.end();
1155
+ return;
1156
+ }
1157
+ if (cleanProvider && !isStripeProjectsExclusiveProvider(cleanProvider)) {
1158
+ const allWs = [
1159
+ workspaceId
1160
+ ? store.getWorkspace(workspaceId)
1161
+ : store.ensureDefaultWorkspace().workspace,
1162
+ ].filter(Boolean);
1163
+ for (const ws of allWs) {
1164
+ if (!ws)
1165
+ continue;
1166
+ const pending = store
1167
+ .listConnections(ws.id)
1168
+ .find((c) => c.providerSlug.toLowerCase() === cleanProvider &&
1169
+ c.redirectUrl &&
1170
+ !isStripeProjectsExclusiveProvider(c.providerSlug));
1171
+ if (pending?.redirectUrl) {
1172
+ const nativeUrl = wrapMuxoConnectUrl(pending.redirectUrl);
1173
+ if (nativeUrl) {
1174
+ if (wantsJson) {
1175
+ sendJson(res, 200, {
1176
+ ok: true,
1177
+ provider: cleanProvider,
1178
+ status: pending.status,
1179
+ redirectUrl: nativeUrl,
1180
+ });
1181
+ return;
1182
+ }
1183
+ res.writeHead(302, { Location: nativeUrl, "Cache-Control": "no-store" });
1184
+ res.end();
1185
+ return;
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ else if (!cleanProvider) {
1191
+ const allWs = [
1192
+ workspaceId
1193
+ ? store.getWorkspace(workspaceId)
1194
+ : store.ensureDefaultWorkspace().workspace,
1195
+ ].filter(Boolean);
1196
+ for (const ws of allWs) {
1197
+ if (!ws)
1198
+ continue;
1199
+ const pending = store
1200
+ .listConnections(ws.id)
1201
+ .find((c) => c.status === "pending" &&
1202
+ c.redirectUrl &&
1203
+ !isStripeProjectsExclusiveProvider(c.providerSlug));
1204
+ if (pending?.redirectUrl) {
1205
+ const nativeUrl = wrapMuxoConnectUrl(pending.redirectUrl);
1206
+ if (nativeUrl &&
1207
+ (/^https:\/\/(?:connect|backend)\.composio\.dev\//i.test(nativeUrl) ||
1208
+ nativeUrl.includes("/oauth/connect/"))) {
1209
+ if (wantsJson) {
1210
+ sendJson(res, 200, {
1211
+ ok: true,
1212
+ provider: pending.providerSlug,
1213
+ status: "pending",
1214
+ redirectUrl: nativeUrl,
1215
+ });
1216
+ return;
1217
+ }
1218
+ res.writeHead(302, { Location: nativeUrl, "Cache-Control": "no-store" });
1219
+ res.end();
1220
+ return;
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ const displayProviderSlug = cleanProvider || "supabase";
1226
+ const displayProviderName = cleanProvider
1227
+ ? cleanProvider.charAt(0).toUpperCase() + cleanProvider.slice(1)
1228
+ : "Supabase & Vercel (Stripe Projects)";
1229
+ if (wantsJson) {
1230
+ sendJson(res, 200, {
1231
+ ok: true,
1232
+ status: "active",
1233
+ lane: "stripe_projects",
1234
+ provider: displayProviderSlug,
1235
+ noOAuthRequired: true,
1236
+ redirectUrl: null,
1237
+ message: "Provisioned Stripe Projects cloud resources (Supabase, Vercel, Cloudflare, Turso, Neon, etc.) are active immediately with zero OAuth required.",
1238
+ });
1239
+ return;
1240
+ }
1241
+ res.writeHead(200, {
1242
+ "Content-Type": "text/html; charset=utf-8",
1243
+ "Cache-Control": "no-store",
1244
+ });
1245
+ res.end(renderOAuthCallbackPageHtml({
1246
+ providerSlug: displayProviderSlug,
1247
+ providerName: displayProviderName,
1248
+ status: "active",
1249
+ workspaceId: workspaceId || "default",
1250
+ externalAccountId: `sp_conn_${displayProviderSlug}`,
1251
+ }));
1252
+ return;
1253
+ }
1254
+ // Muxo OAuth Connection Handoff (GET/POST /oauth/connect/:token or /connect/:token)
1255
+ const connectPageMatch = pathname.match(/^\/(?:oauth\/)?connect\/([^/]+)$/);
1256
+ if (connectPageMatch && (req.method === "GET" || req.method === "POST")) {
1257
+ const token = decodeURIComponent(connectPageMatch[1]);
1258
+ const workspaceId = url.searchParams.get("workspace_id") ||
1259
+ url.searchParams.get("user_id") ||
1260
+ undefined;
1261
+ const providerSlug = url.searchParams.get("provider") ||
1262
+ url.searchParams.get("toolkit") ||
1263
+ undefined;
1264
+ const callbackUrl = url.searchParams.get("callback_url") || undefined;
1265
+ const connectedAccountId = url.searchParams.get("connected_account_id") || undefined;
1266
+ if (req.method === "GET") {
1267
+ const wantsJson = url.searchParams.get("format") === "json" ||
1268
+ String(req.headers.accept || "").includes("application/json");
1269
+ const isTestToken = token.startsWith("lk_test");
1270
+ const forceHtml = url.searchParams.get("render") === "html";
1271
+ // Redirect real Composio link tokens directly to Composio's native hosted OAuth page
1272
+ if (!wantsJson && !isTestToken && !forceHtml && (token.startsWith("lk_") || token.startsWith("s_"))) {
1273
+ const nativeComposioUrl = token.startsWith("s_")
1274
+ ? `https://backend.composio.dev/api/v3/s/${encodeURIComponent(token.slice(2))}`
1275
+ : `https://connect.composio.dev/link/${encodeURIComponent(token)}`;
1276
+ res.writeHead(302, {
1277
+ Location: nativeComposioUrl,
1278
+ "Cache-Control": "no-store",
1279
+ });
1280
+ res.end();
1281
+ return;
1282
+ }
1283
+ const meta = await fetchMuxoConnectLinkMetadata(token, {
1284
+ workspaceId,
1285
+ providerSlug,
1286
+ callbackUrl,
1287
+ connectedAccountId,
1288
+ });
1289
+ if (url.searchParams.get("continue") === "1" ||
1290
+ url.searchParams.get("auto") === "1") {
1291
+ const submitted = await submitMuxoConnectLink(token, {}, {
1292
+ workspaceId,
1293
+ providerSlug: meta.toolkitSlug,
1294
+ callbackUrl: meta.callbackUrl || callbackUrl,
1295
+ connectedAccountId: meta.connectedAccountId,
1296
+ });
1297
+ if (submitted.ok && submitted.redirectUrl) {
1298
+ res.writeHead(302, { Location: submitted.redirectUrl });
1299
+ res.end();
1300
+ return;
1301
+ }
1302
+ }
1303
+ if (wantsJson) {
1304
+ sendJson(res, 200, { ok: true, handoff: meta });
1305
+ return;
1306
+ }
1307
+ res.writeHead(200, {
1308
+ "Content-Type": "text/html; charset=utf-8",
1309
+ "Cache-Control": "no-store",
1310
+ "Referrer-Policy": "no-referrer",
1311
+ });
1312
+ res.end(renderOAuthConnectPageHtml({
1313
+ token: meta.token,
1314
+ toolkitSlug: meta.toolkitSlug,
1315
+ toolkitName: meta.toolkitName,
1316
+ workspaceId,
1317
+ authMode: meta.authMode,
1318
+ status: meta.status,
1319
+ callbackUrl: meta.callbackUrl || callbackUrl,
1320
+ expectedInputFields: meta.expectedInputFields,
1321
+ }));
1322
+ return;
1323
+ }
1324
+ // POST /oauth/connect/:token
1325
+ const rawBody = await readBody(req);
1326
+ let input = {};
1327
+ const contentType = String(req.headers["content-type"] || "");
1328
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1329
+ const formParams = new URLSearchParams(rawBody);
1330
+ for (const [k, v] of formParams.entries()) {
1331
+ if (v !== "")
1332
+ input[k] = v;
1333
+ }
1334
+ }
1335
+ else if (rawBody) {
1336
+ try {
1337
+ const parsed = JSON.parse(rawBody);
1338
+ input =
1339
+ parsed && typeof parsed.input === "object" && parsed.input !== null
1340
+ ? parsed.input
1341
+ : parsed;
1342
+ }
1343
+ catch {
1344
+ input = {};
1345
+ }
1346
+ }
1347
+ const submitted = await submitMuxoConnectLink(token, input, {
1348
+ workspaceId,
1349
+ providerSlug,
1350
+ callbackUrl,
1351
+ connectedAccountId,
1352
+ });
1353
+ const wantsJson = url.searchParams.get("format") === "json" ||
1354
+ String(req.headers.accept || "").includes("application/json") ||
1355
+ contentType.includes("application/json");
1356
+ if (wantsJson) {
1357
+ sendJson(res, submitted.ok ? 200 : 400, {
1358
+ ok: submitted.ok,
1359
+ status: submitted.status,
1360
+ redirectUrl: submitted.redirectUrl,
1361
+ redirect_url: submitted.redirectUrl,
1362
+ connectedAccountId: submitted.connectedAccountId,
1363
+ ...(submitted.error ? { error: submitted.error } : {}),
1364
+ });
1365
+ return;
1366
+ }
1367
+ if (submitted.ok && submitted.redirectUrl) {
1368
+ res.writeHead(302, { Location: submitted.redirectUrl });
1369
+ res.end();
1370
+ return;
1371
+ }
1372
+ const meta = await fetchMuxoConnectLinkMetadata(token, {
1373
+ workspaceId,
1374
+ providerSlug,
1375
+ callbackUrl,
1376
+ connectedAccountId,
1377
+ });
1378
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
1379
+ res.end(renderOAuthConnectPageHtml({
1380
+ token: meta.token,
1381
+ toolkitSlug: meta.toolkitSlug,
1382
+ toolkitName: meta.toolkitName,
1383
+ workspaceId,
1384
+ authMode: meta.authMode,
1385
+ status: meta.status,
1386
+ callbackUrl: meta.callbackUrl || callbackUrl,
1387
+ expectedInputFields: meta.expectedInputFields,
1388
+ error: submitted.error || "Could not start authorization handoff.",
1389
+ }));
1068
1390
  return;
1069
1391
  }
1070
1392
  // One-click Plan Approval Handoff Page (GET /approve/:planId)
@@ -1151,8 +1473,9 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1151
1473
  "Cache-Control": "no-cache",
1152
1474
  Connection: "keep-alive",
1153
1475
  "Mcp-Session-Id": sessionId,
1476
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1154
1477
  "Access-Control-Allow-Origin": "*",
1155
- "Access-Control-Expose-Headers": "Mcp-Session-Id",
1478
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version",
1156
1479
  });
1157
1480
  if (req.method !== "HEAD") {
1158
1481
  res.write(`event: endpoint\ndata: ${JSON.stringify({ uri: `${config.baseUrl}/mcp`, sessionId })}\n\n`);
@@ -1163,15 +1486,20 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1163
1486
  sendJson(res, 200, {
1164
1487
  ok: true,
1165
1488
  transport: "streamable-http",
1166
- protocolVersion: "2025-06-18",
1489
+ protocolVersion: LATEST_MCP_PROTOCOL_VERSION,
1490
+ supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
1167
1491
  serverInfo: {
1168
1492
  name: "muxo-mcp-server",
1493
+ title: "Muxo Unified Agent Runtime & Governance MCP Server",
1169
1494
  version: MUXO_VERSION,
1170
1495
  },
1171
1496
  instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
1172
1497
  toolsCount: MUXO_MCP_TOOLS.length,
1173
1498
  skillsCount: getMuxoMcpSkills().length,
1174
- }, { "Mcp-Session-Id": sessionId });
1499
+ }, {
1500
+ "Mcp-Session-Id": sessionId,
1501
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1502
+ });
1175
1503
  return;
1176
1504
  }
1177
1505
  if (pathname === "/mcp" && req.method === "DELETE") {
@@ -1184,15 +1512,39 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1184
1512
  if (pathname === "/mcp" && req.method === "POST") {
1185
1513
  const raw = await readBody(req);
1186
1514
  const parsed = JSON.parse(raw || "{}");
1515
+ const headerCheck = !Array.isArray(parsed)
1516
+ ? validateMcpHttpHeadersAndMeta({
1517
+ protocolVersion: String(req.headers["mcp-protocol-version"] || ""),
1518
+ method: String(req.headers["mcp-method"] || ""),
1519
+ name: String(req.headers["mcp-name"] || ""),
1520
+ }, parsed)
1521
+ : { ok: true, protocolVersion: LATEST_MCP_PROTOCOL_VERSION };
1522
+ if (!headerCheck.ok) {
1523
+ const fallbackSessionId = String(req.headers["mcp-session-id"] || "") ||
1524
+ `mcp_sess_${crypto.randomBytes(6).toString("hex")}`;
1525
+ sendJson(res, 200, {
1526
+ jsonrpc: "2.0",
1527
+ id: headerCheck.id,
1528
+ error: headerCheck.error,
1529
+ }, {
1530
+ "Mcp-Session-Id": fallbackSessionId,
1531
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1532
+ });
1533
+ return;
1534
+ }
1187
1535
  const resolved = await requireContext(req, res, pathname, !Array.isArray(parsed) && isPublicMcpRequest(parsed));
1188
1536
  if (!resolved)
1189
1537
  return;
1190
1538
  const { ctx } = resolved;
1191
1539
  const sessionId = String(req.headers["mcp-session-id"] || "") ||
1192
1540
  `mcp_sess_${ctx.workspace.id}`;
1541
+ const responseHeaders = {
1542
+ "Mcp-Session-Id": sessionId,
1543
+ "MCP-Protocol-Version": headerCheck.protocolVersion,
1544
+ };
1193
1545
  if (Array.isArray(parsed)) {
1194
1546
  const responses = (await Promise.all(parsed.map((item) => handleJsonRpcMcpRequest(engine, ctx, (item || {}))))).filter((r) => r !== null);
1195
- sendJson(res, 200, responses, { "Mcp-Session-Id": sessionId });
1547
+ sendJson(res, 200, responses, responseHeaders);
1196
1548
  return;
1197
1549
  }
1198
1550
  const rpc = parsed;
@@ -1200,12 +1552,12 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1200
1552
  if (!response) {
1201
1553
  res.writeHead(202, {
1202
1554
  "Access-Control-Allow-Origin": "*",
1203
- "Mcp-Session-Id": sessionId,
1555
+ ...responseHeaders,
1204
1556
  });
1205
1557
  res.end();
1206
1558
  return;
1207
1559
  }
1208
- sendJson(res, 200, response, { "Mcp-Session-Id": sessionId });
1560
+ sendJson(res, 200, response, responseHeaders);
1209
1561
  return;
1210
1562
  }
1211
1563
  // Auth Endpoints
@@ -1263,7 +1615,11 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1263
1615
  const isPublicRoute = ((pathname === "/api/v1/tools" || pathname === "/v1/tools/search") &&
1264
1616
  (req.method === "GET" || req.method === "POST")) ||
1265
1617
  (pathname.startsWith("/api/v1/tools/") && req.method === "GET") ||
1266
- (pathname === "/api/v1/billing/webhook" && req.method === "POST");
1618
+ (pathname === "/api/v1/billing/webhook" && req.method === "POST") ||
1619
+ ((pathname === "/webhooks" ||
1620
+ pathname === "/webhooks/composio" ||
1621
+ pathname === "/api/v1/triggers/webhook") &&
1622
+ req.method === "POST");
1267
1623
  const resolvedCtx = await requireContext(req, res, pathname, isPublicRoute);
1268
1624
  if (!resolvedCtx)
1269
1625
  return;
@@ -1277,6 +1633,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1277
1633
  profile: engine.getProfile(ctx),
1278
1634
  organization: org,
1279
1635
  governance: engine.governance.getStatus(),
1636
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1280
1637
  plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1281
1638
  activeMemoryDefaults: guardrailsSummary.activeDefaults,
1282
1639
  activeGuardrails: guardrailsSummary.activeGuardrails,
@@ -1451,6 +1808,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1451
1808
  auditChain: fullReport.auditChain,
1452
1809
  },
1453
1810
  workspacePolicy: ws.policy,
1811
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1454
1812
  plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1455
1813
  activeMemoryDefaults: guardrailsSummary.activeDefaults,
1456
1814
  activeGuardrails: guardrailsSummary.activeGuardrails,
@@ -1465,16 +1823,37 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1465
1823
  });
1466
1824
  return;
1467
1825
  }
1826
+ if ((pathname === "/api/v1/guardrails/reset" || pathname === "/api/v1/policy/reset") &&
1827
+ req.method === "POST") {
1828
+ const resetResult = await engine.resetWorkspaceGuardrails(ctx);
1829
+ sendJson(res, 200, {
1830
+ ...resetResult,
1831
+ humanMessage: "Reset workspace guardrails to Safe Default.",
1832
+ });
1833
+ return;
1834
+ }
1835
+ if ((pathname === "/api/v1/guardrails/check" ||
1836
+ pathname === "/api/v1/guardrails/simulate" ||
1837
+ pathname === "/api/v1/policy/check") &&
1838
+ req.method === "POST") {
1839
+ const body = JSON.parse((await readBody(req)) || "{}");
1840
+ const toolSlug = String(body.tool_slug || body.toolSlug || body.slug || body.tool_id || "instant.web.search").trim();
1841
+ const input = (body.input || body.arguments) || {};
1842
+ const result = await engine.checkToolPolicy(ctx, { toolSlug, input });
1843
+ const summary = engine.getGuardrailsSummary(ctx);
1844
+ sendJson(res, 200, {
1845
+ ...result,
1846
+ simulation: result,
1847
+ llmPolicyBrief: summary.llmPolicyBrief,
1848
+ });
1849
+ return;
1850
+ }
1468
1851
  if (pathname === "/api/v1/guardrails" && req.method === "POST") {
1469
1852
  const body = JSON.parse((await readBody(req)) || "{}");
1470
1853
  if (body.reset === true) {
1471
- const updated = store.updateWorkspacePolicy(ctx.workspace.id, DEFAULT_WORKSPACE_POLICY);
1472
- const plainEnglish = describePolicyInPlainEnglish(updated.policy);
1854
+ const resetResult = await engine.resetWorkspaceGuardrails(ctx);
1473
1855
  sendJson(res, 200, {
1474
- ok: true,
1475
- workspace: updated,
1476
- policy: updated.policy,
1477
- plainEnglish,
1856
+ ...resetResult,
1478
1857
  humanMessage: "Reset workspace guardrails to Safe Default.",
1479
1858
  });
1480
1859
  return;
@@ -2968,52 +3347,143 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
2968
3347
  }
2969
3348
  if (pathname === "/api/v1/triggers" && req.method === "GET") {
2970
3349
  const provider = url.searchParams.get("provider") || undefined;
3350
+ const triggerSlug = url.searchParams.get("triggerSlug") || url.searchParams.get("trigger_slug") || undefined;
3351
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
2971
3352
  const available = engine.composioSdk.listTriggerTypes(provider);
2972
3353
  const subscriptions = engine.composioSdk.listActiveTriggers(ctx.workspace.id);
2973
3354
  const events = engine.composioSdk.listTriggerEvents(ctx.workspace.id, 25);
3355
+ const triggerType = triggerSlug
3356
+ ? await engine.composioSdk.getTriggerType(triggerSlug, composioByok)
3357
+ : undefined;
2974
3358
  sendJson(res, 200, {
2975
3359
  ok: true,
2976
3360
  availableTriggers: available,
2977
3361
  activeSubscriptions: subscriptions,
2978
3362
  recentEvents: events,
3363
+ webhookConfig: engine.composioSdk.getWebhookConfigInfo(),
3364
+ ...(triggerType ? { triggerType } : {}),
2979
3365
  });
2980
3366
  return;
2981
3367
  }
2982
3368
  if (pathname === "/api/v1/triggers" && req.method === "POST") {
2983
3369
  const composioByok = store.getByokKey(ctx.workspace.id, "composio");
2984
3370
  const body = JSON.parse((await readBody(req)) || "{}");
3371
+ const rawConfig = body.triggerConfig && typeof body.triggerConfig === "object"
3372
+ ? body.triggerConfig
3373
+ : body.config && typeof body.config === "object"
3374
+ ? body.config
3375
+ : {};
2985
3376
  const subscription = await engine.composioSdk.enableTrigger({
2986
3377
  workspaceId: ctx.workspace.id,
3378
+ userId: body.userId ? String(body.userId) : undefined,
2987
3379
  triggerSlug: String(body.triggerSlug || body.trigger_slug || "GITHUB_COMMIT_EVENT"),
2988
3380
  providerSlug: String(body.providerSlug || body.provider || "github"),
2989
3381
  connectedAccountId: body.connectedAccountId
2990
3382
  ? String(body.connectedAccountId)
2991
3383
  : undefined,
2992
3384
  webhookUrl: body.webhookUrl ? String(body.webhookUrl) : undefined,
2993
- config: body.config && typeof body.config === "object" ? body.config : {},
3385
+ config: rawConfig,
3386
+ subscribe: body.subscribe === true,
2994
3387
  composioApiKey: composioByok,
2995
3388
  });
2996
3389
  sendJson(res, 201, { ok: true, subscription });
2997
3390
  return;
2998
3391
  }
2999
- if (pathname === "/api/v1/triggers/webhook" && req.method === "POST") {
3000
- const body = JSON.parse((await readBody(req)) || "{}");
3001
- const triggerSlug = String(body.triggerSlug || body.type || "GITHUB_COMMIT_EVENT").toUpperCase();
3002
- const providerSlug = String(body.providerSlug || triggerSlug.split("_")[0] || "github").toLowerCase();
3003
- const payloadObj = body.payload && typeof body.payload === "object"
3004
- ? body.payload
3005
- : body;
3006
- const rawPayloadText = JSON.stringify(payloadObj);
3392
+ if ((pathname === "/api/v1/triggers/webhook" ||
3393
+ pathname === "/webhooks" ||
3394
+ pathname === "/webhooks/composio") &&
3395
+ req.method === "POST") {
3396
+ const rawBody = await readBody(req);
3397
+ const sigHeader = String(req.headers["webhook-signature"] ||
3398
+ req.headers["x-composio-webhook-signature"] ||
3399
+ "").trim();
3400
+ const webhookIdHeader = String(req.headers["webhook-id"] || req.headers["x-composio-webhook-id"] || "").trim();
3401
+ const webhookTimestampHeader = String(req.headers["webhook-timestamp"] ||
3402
+ req.headers["x-composio-webhook-timestamp"] ||
3403
+ "").trim();
3404
+ const webhookVersionHeader = String(req.headers["x-composio-webhook-version"] || "").trim();
3405
+ let signatureVerified = false;
3406
+ if (sigHeader) {
3407
+ const check = await verifyComposioWebhookSignature({
3408
+ rawPayload: rawBody,
3409
+ signatureHeader: sigHeader,
3410
+ webhookIdHeader,
3411
+ webhookTimestampHeader,
3412
+ secret: config.composioWebhookSecret,
3413
+ });
3414
+ if (config.composioWebhookSecret && !check.verified) {
3415
+ sendJson(res, 401, {
3416
+ ok: false,
3417
+ error: `Invalid Composio webhook signature (${check.reason || "verification_failed"})`,
3418
+ });
3419
+ return;
3420
+ }
3421
+ signatureVerified = check.verified;
3422
+ }
3423
+ const body = JSON.parse(rawBody || "{}");
3424
+ const envelope = parseComposioWebhookEnvelope(body, {
3425
+ webhookId: webhookIdHeader || undefined,
3426
+ webhookTimestamp: webhookTimestampHeader || undefined,
3427
+ webhookVersion: webhookVersionHeader || undefined,
3428
+ });
3429
+ let targetWorkspaceId = ctx.workspace.id;
3430
+ if (envelope.userId && store.getWorkspace(envelope.userId)) {
3431
+ targetWorkspaceId = envelope.userId;
3432
+ }
3433
+ if (envelope.eventType === "composio.connected_account.activated" ||
3434
+ envelope.eventType === "composio.connected_account.expired") {
3435
+ const nextStatus = envelope.eventType === "composio.connected_account.activated"
3436
+ ? "active"
3437
+ : "expired";
3438
+ store.upsertConnection({
3439
+ workspaceId: targetWorkspaceId,
3440
+ lane: "composio_sdk",
3441
+ providerSlug: envelope.providerSlug,
3442
+ providerName: envelope.providerSlug.charAt(0).toUpperCase() +
3443
+ envelope.providerSlug.slice(1),
3444
+ status: nextStatus,
3445
+ externalAccountId: envelope.connectedAccountId || null,
3446
+ metadata: {
3447
+ webhookEvent: envelope.eventType,
3448
+ webhookId: envelope.webhookId,
3449
+ updatedViaWebhookAt: new Date().toISOString(),
3450
+ },
3451
+ });
3452
+ if (nextStatus === "expired") {
3453
+ engine.composioSdk.disableTriggersMatching({
3454
+ workspaceId: targetWorkspaceId,
3455
+ connectedAccountId: envelope.connectedAccountId,
3456
+ });
3457
+ }
3458
+ }
3459
+ else if (envelope.eventType === "composio.trigger.disabled") {
3460
+ engine.composioSdk.disableTriggersMatching({
3461
+ workspaceId: targetWorkspaceId,
3462
+ triggerId: envelope.triggerId,
3463
+ triggerSlug: envelope.triggerSlug,
3464
+ connectedAccountId: envelope.connectedAccountId,
3465
+ });
3466
+ }
3467
+ const rawPayloadText = JSON.stringify(envelope.payloadObj);
3007
3468
  const injectionScan = engine.governance.scanMemoryContent({
3008
- workspaceId: ctx.workspace.id,
3469
+ workspaceId: targetWorkspaceId,
3009
3470
  content: rawPayloadText,
3010
3471
  });
3011
3472
  const disposition = injectionScan.safe ? "dispatched" : "quarantined";
3012
3473
  const eventRecord = {
3013
3474
  id: `trgevt_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
3014
- workspaceId: ctx.workspace.id,
3015
- triggerSlug,
3016
- providerSlug,
3475
+ workspaceId: targetWorkspaceId,
3476
+ eventType: envelope.eventType,
3477
+ webhookVersion: envelope.webhookVersion,
3478
+ signatureVerified,
3479
+ webhookId: envelope.webhookId,
3480
+ triggerId: envelope.triggerId,
3481
+ triggerSlug: envelope.triggerSlug,
3482
+ providerSlug: envelope.providerSlug,
3483
+ connectedAccountId: envelope.connectedAccountId,
3484
+ userId: envelope.userId,
3485
+ projectId: envelope.projectId,
3486
+ orgId: envelope.orgId,
3017
3487
  receivedAt: new Date().toISOString(),
3018
3488
  promptInjectionScan: {
3019
3489
  safe: injectionScan.safe,
@@ -3021,7 +3491,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
3021
3491
  findings: injectionScan.findings.map((f) => `${f.patternId}: ${f.description}`),
3022
3492
  },
3023
3493
  disposition: disposition,
3024
- payloadPreview: payloadObj,
3494
+ payloadPreview: envelope.payloadObj,
3025
3495
  };
3026
3496
  engine.composioSdk.recordTriggerEvent(eventRecord);
3027
3497
  sendJson(res, injectionScan.safe ? 200 : 202, {