@muxoai/platform 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +22 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.codex/config.toml +5 -0
- package/README.md +57 -28
- package/dist/cli.js +328 -10
- package/dist/engine.d.ts +28 -0
- package/dist/engine.js +194 -27
- package/dist/mcp.d.ts +36 -0
- package/dist/mcp.js +534 -21
- package/dist/nl.d.ts +34 -0
- package/dist/nl.js +577 -0
- package/dist/providers/composio-instant.js +442 -0
- package/dist/providers/composio-sdk.js +88 -30
- package/dist/providers/stripe-billing.d.ts +13 -3
- package/dist/providers/stripe-billing.js +240 -36
- package/dist/providers/stripe-projects.js +14 -3
- package/dist/sdk.d.ts +84 -1
- package/dist/sdk.js +53 -0
- package/dist/server.js +476 -15
- package/dist/store.d.ts +18 -2
- package/dist/store.js +197 -8
- package/dist/types.d.ts +14 -2
- package/dist/ui/console-html.js +216 -26
- package/package.json +4 -1
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +12 -8
- package/plugins/muxo/.mcp.json +33 -1
- package/plugins/muxo/mcp.json +38 -0
- package/plugins/muxo/plugin.json +59 -0
- package/plugins/muxo/skills/setup/SKILL.md +28 -0
package/dist/server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import http from "node:http";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { getConfig, ROOT_DIR } from "./config.js";
|
|
5
6
|
import { MuxoEngine } from "./engine.js";
|
|
6
|
-
import { handleJsonRpcMcpRequest, MUXO_MCP_TOOLS } from "./mcp.js";
|
|
7
|
+
import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.js";
|
|
7
8
|
import { MuxoStore } from "./store.js";
|
|
8
9
|
import { renderConsoleHtml, renderLandingHtml } from "./ui/console-html.js";
|
|
9
10
|
function renderSkillMarkdown(baseUrl) {
|
|
@@ -82,13 +83,15 @@ async function readBody(req) {
|
|
|
82
83
|
}
|
|
83
84
|
return Buffer.concat(chunks).toString("utf8");
|
|
84
85
|
}
|
|
85
|
-
function sendJson(res, status, payload) {
|
|
86
|
+
function sendJson(res, status, payload, extraHeaders) {
|
|
86
87
|
const body = JSON.stringify(payload, null, 2);
|
|
87
88
|
res.writeHead(status, {
|
|
88
89
|
"Content-Type": "application/json; charset=utf-8",
|
|
89
90
|
"Access-Control-Allow-Origin": "*",
|
|
90
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
91
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id",
|
|
92
|
+
"Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
|
|
91
93
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
94
|
+
...(extraHeaders || {}),
|
|
92
95
|
});
|
|
93
96
|
res.end(body);
|
|
94
97
|
}
|
|
@@ -97,6 +100,23 @@ export function createMuxoServer(customConfig) {
|
|
|
97
100
|
const store = new MuxoStore(config.dbPath, config.vaultMasterKey);
|
|
98
101
|
const engine = new MuxoEngine(config, store);
|
|
99
102
|
const bootstrap = store.ensureDefaultWorkspace();
|
|
103
|
+
// OAuth 2.1 DCR + PKCE Authorization Code store for OpenAI Plugin / MCP clients
|
|
104
|
+
const oauthClients = new Map();
|
|
105
|
+
oauthClients.set("muxo-openai-plugin", {
|
|
106
|
+
client_id: "muxo-openai-plugin",
|
|
107
|
+
client_name: "Muxo Official OpenAI Plugin",
|
|
108
|
+
redirect_uris: [
|
|
109
|
+
"https://chatgpt.com/connector_platform_oauth_redirect",
|
|
110
|
+
"https://platform.openai.com/apps-manage/oauth",
|
|
111
|
+
"http://127.0.0.1:3118/oauth/callback",
|
|
112
|
+
"http://localhost:3118/oauth/callback",
|
|
113
|
+
],
|
|
114
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
115
|
+
response_types: ["code"],
|
|
116
|
+
token_endpoint_auth_method: "none",
|
|
117
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
118
|
+
});
|
|
119
|
+
const oauthCodes = new Map();
|
|
100
120
|
const resolveContext = (req) => {
|
|
101
121
|
const authHeader = String(req.headers.authorization || "").trim();
|
|
102
122
|
if (authHeader.toLowerCase().startsWith("bearer ")) {
|
|
@@ -134,7 +154,7 @@ export function createMuxoServer(customConfig) {
|
|
|
134
154
|
ok: true,
|
|
135
155
|
status: "ready",
|
|
136
156
|
service: "muxo-platform",
|
|
137
|
-
version: "1.
|
|
157
|
+
version: "1.3.0",
|
|
138
158
|
environment: "production",
|
|
139
159
|
live_execution: true,
|
|
140
160
|
customer_billing: true,
|
|
@@ -167,8 +187,9 @@ export function createMuxoServer(customConfig) {
|
|
|
167
187
|
if ((pathname === "/.well-known/oauth-protected-resource" ||
|
|
168
188
|
pathname === "/.well-known/oauth-protected-resource/mcp") &&
|
|
169
189
|
req.method === "GET") {
|
|
190
|
+
const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
|
|
170
191
|
const requestOrigin = req.headers.host
|
|
171
|
-
?
|
|
192
|
+
? `${proto}://${req.headers.host}`
|
|
172
193
|
: config.baseUrl;
|
|
173
194
|
const resource = pathname === "/.well-known/oauth-protected-resource/mcp"
|
|
174
195
|
? `${requestOrigin}/mcp`
|
|
@@ -183,13 +204,229 @@ export function createMuxoServer(customConfig) {
|
|
|
183
204
|
});
|
|
184
205
|
return;
|
|
185
206
|
}
|
|
207
|
+
if ((pathname === "/.well-known/oauth-authorization-server" ||
|
|
208
|
+
pathname === "/.well-known/openid-configuration" ||
|
|
209
|
+
pathname === "/.well-known/oauth-authorization-server/mcp") &&
|
|
210
|
+
req.method === "GET") {
|
|
211
|
+
const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
|
|
212
|
+
const requestOrigin = req.headers.host
|
|
213
|
+
? `${proto}://${req.headers.host}`
|
|
214
|
+
: config.baseUrl;
|
|
215
|
+
sendJson(res, 200, {
|
|
216
|
+
issuer: requestOrigin,
|
|
217
|
+
authorization_endpoint: `${requestOrigin}/oauth/authorize`,
|
|
218
|
+
token_endpoint: `${requestOrigin}/oauth/token`,
|
|
219
|
+
registration_endpoint: `${requestOrigin}/oauth/register`,
|
|
220
|
+
userinfo_endpoint: `${requestOrigin}/oauth/userinfo`,
|
|
221
|
+
jwks_uri: `${requestOrigin}/.well-known/jwks.json`,
|
|
222
|
+
scopes_supported: [
|
|
223
|
+
"muxo:mcp",
|
|
224
|
+
"muxo:read",
|
|
225
|
+
"muxo:execute",
|
|
226
|
+
"muxo:provision",
|
|
227
|
+
"openid",
|
|
228
|
+
"profile",
|
|
229
|
+
"email",
|
|
230
|
+
],
|
|
231
|
+
response_types_supported: ["code"],
|
|
232
|
+
grant_types_supported: [
|
|
233
|
+
"authorization_code",
|
|
234
|
+
"refresh_token",
|
|
235
|
+
"client_credentials",
|
|
236
|
+
],
|
|
237
|
+
token_endpoint_auth_methods_supported: [
|
|
238
|
+
"none",
|
|
239
|
+
"client_secret_post",
|
|
240
|
+
"client_secret_basic",
|
|
241
|
+
],
|
|
242
|
+
code_challenge_methods_supported: ["S256", "plain"],
|
|
243
|
+
client_id_metadata_document_supported: true,
|
|
244
|
+
service_documentation: `${config.docsUrl}/quickstart`,
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (pathname === "/oauth/register" && req.method === "POST") {
|
|
249
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
250
|
+
const clientId = typeof body.client_id === "string" && body.client_id.trim()
|
|
251
|
+
? body.client_id.trim()
|
|
252
|
+
: `muxo_client_${crypto.randomBytes(8).toString("hex")}`;
|
|
253
|
+
const clientRecord = {
|
|
254
|
+
client_id: clientId,
|
|
255
|
+
client_name: String(body.client_name || "OpenAI Plugin MCP Client"),
|
|
256
|
+
redirect_uris: Array.isArray(body.redirect_uris)
|
|
257
|
+
? body.redirect_uris.map(String)
|
|
258
|
+
: [
|
|
259
|
+
"https://chatgpt.com/connector_platform_oauth_redirect",
|
|
260
|
+
"http://127.0.0.1:3118/oauth/callback",
|
|
261
|
+
],
|
|
262
|
+
grant_types: Array.isArray(body.grant_types)
|
|
263
|
+
? body.grant_types.map(String)
|
|
264
|
+
: ["authorization_code", "refresh_token"],
|
|
265
|
+
response_types: Array.isArray(body.response_types)
|
|
266
|
+
? body.response_types.map(String)
|
|
267
|
+
: ["code"],
|
|
268
|
+
token_endpoint_auth_method: String(body.token_endpoint_auth_method || "none"),
|
|
269
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
270
|
+
};
|
|
271
|
+
oauthClients.set(clientId, clientRecord);
|
|
272
|
+
sendJson(res, 201, clientRecord);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (pathname === "/oauth/authorize" &&
|
|
276
|
+
(req.method === "GET" || req.method === "POST")) {
|
|
277
|
+
const bodyParams = req.method === "POST"
|
|
278
|
+
? new URLSearchParams(await readBody(req))
|
|
279
|
+
: url.searchParams;
|
|
280
|
+
const clientId = bodyParams.get("client_id") ||
|
|
281
|
+
url.searchParams.get("client_id") ||
|
|
282
|
+
"muxo-openai-plugin";
|
|
283
|
+
const redirectUri = bodyParams.get("redirect_uri") ||
|
|
284
|
+
url.searchParams.get("redirect_uri") ||
|
|
285
|
+
"http://127.0.0.1:3118/oauth/callback";
|
|
286
|
+
const stateParam = bodyParams.get("state") || url.searchParams.get("state") || "";
|
|
287
|
+
const scope = bodyParams.get("scope") ||
|
|
288
|
+
url.searchParams.get("scope") ||
|
|
289
|
+
"muxo:mcp muxo:read muxo:execute muxo:provision";
|
|
290
|
+
const codeChallenge = bodyParams.get("code_challenge") ||
|
|
291
|
+
url.searchParams.get("code_challenge") ||
|
|
292
|
+
undefined;
|
|
293
|
+
const codeChallengeMethod = bodyParams.get("code_challenge_method") ||
|
|
294
|
+
url.searchParams.get("code_challenge_method") ||
|
|
295
|
+
"S256";
|
|
296
|
+
const code = `muxo_auth_code_${crypto.randomBytes(16).toString("hex")}`;
|
|
297
|
+
const issuedKey = bootstrap.defaultApiKey ||
|
|
298
|
+
store.createApiKey({
|
|
299
|
+
workspaceId: bootstrap.workspace.id,
|
|
300
|
+
name: `OAuth Client (${clientId})`,
|
|
301
|
+
environment: "live",
|
|
302
|
+
}).secretKey;
|
|
303
|
+
oauthCodes.set(code, {
|
|
304
|
+
code,
|
|
305
|
+
clientId,
|
|
306
|
+
redirectUri,
|
|
307
|
+
scope,
|
|
308
|
+
codeChallenge,
|
|
309
|
+
codeChallengeMethod,
|
|
310
|
+
workspaceId: bootstrap.workspace.id,
|
|
311
|
+
apiKey: issuedKey,
|
|
312
|
+
expiresAt: Date.now() + 10 * 60 * 1000,
|
|
313
|
+
});
|
|
314
|
+
const redirectTarget = new URL(redirectUri);
|
|
315
|
+
redirectTarget.searchParams.set("code", code);
|
|
316
|
+
if (stateParam)
|
|
317
|
+
redirectTarget.searchParams.set("state", stateParam);
|
|
318
|
+
if (url.searchParams.get("format") === "json" ||
|
|
319
|
+
String(req.headers.accept || "").includes("application/json")) {
|
|
320
|
+
sendJson(res, 200, {
|
|
321
|
+
ok: true,
|
|
322
|
+
code,
|
|
323
|
+
state: stateParam || null,
|
|
324
|
+
client_id: clientId,
|
|
325
|
+
redirect_uri: redirectTarget.toString(),
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
res.writeHead(302, {
|
|
330
|
+
Location: redirectTarget.toString(),
|
|
331
|
+
"Access-Control-Allow-Origin": "*",
|
|
332
|
+
});
|
|
333
|
+
res.end();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (pathname === "/oauth/token" && req.method === "POST") {
|
|
337
|
+
const rawBody = await readBody(req);
|
|
338
|
+
const contentType = String(req.headers["content-type"] || "");
|
|
339
|
+
const params = {};
|
|
340
|
+
if (contentType.includes("application/json")) {
|
|
341
|
+
const parsed = JSON.parse(rawBody || "{}");
|
|
342
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
343
|
+
if (v !== undefined && v !== null)
|
|
344
|
+
params[k] = String(v);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const sp = new URLSearchParams(rawBody);
|
|
349
|
+
for (const [k, v] of sp.entries()) {
|
|
350
|
+
params[k] = v;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const grantType = params.grant_type || "authorization_code";
|
|
354
|
+
const code = params.code || "";
|
|
355
|
+
const codeVerifier = params.code_verifier || "";
|
|
356
|
+
let accessToken = bootstrap.defaultApiKey || "";
|
|
357
|
+
let scope = params.scope || "muxo:mcp muxo:read muxo:execute muxo:provision";
|
|
358
|
+
if (grantType === "authorization_code" && code) {
|
|
359
|
+
const record = oauthCodes.get(code);
|
|
360
|
+
if (!record || record.expiresAt < Date.now()) {
|
|
361
|
+
sendJson(res, 400, {
|
|
362
|
+
error: "invalid_grant",
|
|
363
|
+
error_description: "Authorization code is invalid or expired.",
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (record.codeChallenge) {
|
|
368
|
+
if (!codeVerifier) {
|
|
369
|
+
sendJson(res, 400, {
|
|
370
|
+
error: "invalid_grant",
|
|
371
|
+
error_description: "PKCE code_verifier is required.",
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const method = (record.codeChallengeMethod || "S256").toUpperCase();
|
|
376
|
+
const computed = method === "S256"
|
|
377
|
+
? crypto
|
|
378
|
+
.createHash("sha256")
|
|
379
|
+
.update(codeVerifier)
|
|
380
|
+
.digest("base64url")
|
|
381
|
+
: codeVerifier;
|
|
382
|
+
if (computed !== record.codeChallenge) {
|
|
383
|
+
sendJson(res, 400, {
|
|
384
|
+
error: "invalid_grant",
|
|
385
|
+
error_description: "PKCE code_verifier mismatch.",
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
accessToken = record.apiKey;
|
|
391
|
+
scope = record.scope;
|
|
392
|
+
oauthCodes.delete(code);
|
|
393
|
+
}
|
|
394
|
+
else if (!accessToken) {
|
|
395
|
+
accessToken = store.createApiKey({
|
|
396
|
+
workspaceId: bootstrap.workspace.id,
|
|
397
|
+
name: "OAuth Token Key",
|
|
398
|
+
environment: "live",
|
|
399
|
+
}).secretKey;
|
|
400
|
+
}
|
|
401
|
+
sendJson(res, 200, {
|
|
402
|
+
access_token: accessToken,
|
|
403
|
+
token_type: "Bearer",
|
|
404
|
+
expires_in: 3600,
|
|
405
|
+
refresh_token: `muxo_rt_${crypto.randomBytes(16).toString("hex")}`,
|
|
406
|
+
scope,
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (pathname === "/oauth/userinfo" && req.method === "GET") {
|
|
411
|
+
const ctx = resolveContext(req);
|
|
412
|
+
sendJson(res, 200, {
|
|
413
|
+
sub: ctx.user.id,
|
|
414
|
+
id: ctx.user.id,
|
|
415
|
+
name: ctx.user.name,
|
|
416
|
+
email: ctx.user.email,
|
|
417
|
+
workspace_id: ctx.workspace.id,
|
|
418
|
+
workspace_name: ctx.workspace.name,
|
|
419
|
+
tier: ctx.workspace.tier,
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
186
423
|
if ((pathname === "/api/openapi.json" || pathname === "/openapi.json") &&
|
|
187
424
|
req.method === "GET") {
|
|
188
425
|
sendJson(res, 200, {
|
|
189
426
|
openapi: "3.1.0",
|
|
190
427
|
info: {
|
|
191
428
|
title: "Muxo Unified Agent Execution, Infrastructure, Memory & Governance API",
|
|
192
|
-
version: "1.
|
|
429
|
+
version: "1.3.0",
|
|
193
430
|
description: "One governed boundary over Connected Apps (Composio SDK), Instant Tools (Composio Instant), Cloud Infrastructure (Stripe Projects), Supermemory v5 Brain, and Microsoft Agent Governance Toolkit.",
|
|
194
431
|
},
|
|
195
432
|
servers: [{ url: config.baseUrl }],
|
|
@@ -273,10 +510,12 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
273
510
|
res.end(`# Muxo\n> Unified execution, cloud provisioning, Supermemory v5 brain, and Microsoft Agent Governance boundary.\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`);
|
|
274
511
|
return;
|
|
275
512
|
}
|
|
276
|
-
// Serve Installable Muxo Plugin Manifests & Metadata
|
|
513
|
+
// Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude)
|
|
277
514
|
const pluginJsonRoutes = {
|
|
278
515
|
"/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
|
|
279
516
|
"/.claude-plugin/marketplace.json": path.join(ROOT_DIR, ".claude-plugin", "marketplace.json"),
|
|
517
|
+
"/plugins/muxo/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"),
|
|
518
|
+
"/plugins/muxo/mcp.json": path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"),
|
|
280
519
|
"/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
|
|
281
520
|
"/plugins/muxo/.claude-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".claude-plugin", "plugin.json"),
|
|
282
521
|
"/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
|
|
@@ -289,15 +528,66 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
289
528
|
return;
|
|
290
529
|
}
|
|
291
530
|
}
|
|
531
|
+
const pluginTextRoutes = {
|
|
532
|
+
"/plugins/muxo/skills/muxo/SKILL.md": {
|
|
533
|
+
file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "SKILL.md"),
|
|
534
|
+
contentType: "text/markdown; charset=utf-8",
|
|
535
|
+
},
|
|
536
|
+
"/plugins/muxo/skills/muxo/references/cli.md": {
|
|
537
|
+
file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "cli.md"),
|
|
538
|
+
contentType: "text/markdown; charset=utf-8",
|
|
539
|
+
},
|
|
540
|
+
"/plugins/muxo/skills/muxo/references/mcp.md": {
|
|
541
|
+
file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "mcp.md"),
|
|
542
|
+
contentType: "text/markdown; charset=utf-8",
|
|
543
|
+
},
|
|
544
|
+
"/plugins/muxo/skills/setup/SKILL.md": {
|
|
545
|
+
file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "setup", "SKILL.md"),
|
|
546
|
+
contentType: "text/markdown; charset=utf-8",
|
|
547
|
+
},
|
|
548
|
+
"/.codex/config.toml": {
|
|
549
|
+
file: path.join(ROOT_DIR, ".codex", "config.toml"),
|
|
550
|
+
contentType: "text/plain; charset=utf-8",
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
if (pluginTextRoutes[pathname] && req.method === "GET") {
|
|
554
|
+
const entry = pluginTextRoutes[pathname];
|
|
555
|
+
if (fs.existsSync(entry.file)) {
|
|
556
|
+
res.writeHead(200, {
|
|
557
|
+
"Content-Type": entry.contentType,
|
|
558
|
+
"Access-Control-Allow-Origin": "*",
|
|
559
|
+
});
|
|
560
|
+
res.end(fs.readFileSync(entry.file, "utf8"));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
292
564
|
if (pathname === "/api/v1/plugin" && req.method === "GET") {
|
|
565
|
+
const portableManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"), "utf8"));
|
|
566
|
+
const mcpConfig = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"), "utf8"));
|
|
293
567
|
const codexManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"), "utf8"));
|
|
294
568
|
sendJson(res, 200, {
|
|
295
569
|
ok: true,
|
|
296
|
-
plugin:
|
|
570
|
+
plugin: portableManifest,
|
|
571
|
+
codexPlugin: codexManifest,
|
|
572
|
+
mcp: mcpConfig,
|
|
573
|
+
skills: getMuxoMcpSkills().map((s) => ({
|
|
574
|
+
name: s.name,
|
|
575
|
+
description: s.description,
|
|
576
|
+
uri: s.uri,
|
|
577
|
+
version: s.version,
|
|
578
|
+
files: s.files.length,
|
|
579
|
+
})),
|
|
297
580
|
marketplaces: {
|
|
298
581
|
openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
|
|
299
582
|
claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
|
|
300
583
|
},
|
|
584
|
+
manifests: {
|
|
585
|
+
portablePluginJson: `${config.baseUrl}/plugins/muxo/plugin.json`,
|
|
586
|
+
portableMcpJson: `${config.baseUrl}/plugins/muxo/mcp.json`,
|
|
587
|
+
onboardingSkill: `${config.baseUrl}/plugins/muxo/skills/setup/SKILL.md`,
|
|
588
|
+
codexPluginJson: `${config.baseUrl}/plugins/muxo/.codex-plugin/plugin.json`,
|
|
589
|
+
codexConfigToml: `${config.baseUrl}/.codex/config.toml`,
|
|
590
|
+
},
|
|
301
591
|
installCommands: {
|
|
302
592
|
codex: "codex plugin add muxo@muxo",
|
|
303
593
|
cliSignup: "muxo login --signup --name my-agent",
|
|
@@ -445,18 +735,66 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
445
735
|
});
|
|
446
736
|
return;
|
|
447
737
|
}
|
|
448
|
-
// Streamable HTTP MCP Endpoint (POST /mcp)
|
|
738
|
+
// Streamable HTTP MCP Endpoint (POST /mcp, GET /mcp, DELETE /mcp)
|
|
739
|
+
if (pathname === "/mcp" && req.method === "GET") {
|
|
740
|
+
const accept = String(req.headers.accept || "");
|
|
741
|
+
const sessionId = String(req.headers["mcp-session-id"] || "") ||
|
|
742
|
+
`mcp_sess_${crypto.randomBytes(8).toString("hex")}`;
|
|
743
|
+
if (accept.includes("text/event-stream")) {
|
|
744
|
+
res.writeHead(200, {
|
|
745
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
746
|
+
"Cache-Control": "no-cache",
|
|
747
|
+
Connection: "keep-alive",
|
|
748
|
+
"Mcp-Session-Id": sessionId,
|
|
749
|
+
"Access-Control-Allow-Origin": "*",
|
|
750
|
+
});
|
|
751
|
+
res.write(`event: endpoint\ndata: ${JSON.stringify({ uri: `${config.baseUrl}/mcp`, sessionId })}\n\n`);
|
|
752
|
+
res.end();
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
sendJson(res, 200, {
|
|
756
|
+
ok: true,
|
|
757
|
+
transport: "streamable-http",
|
|
758
|
+
protocolVersion: "2025-06-18",
|
|
759
|
+
serverInfo: {
|
|
760
|
+
name: "muxo-mcp-server",
|
|
761
|
+
version: "1.3.0",
|
|
762
|
+
},
|
|
763
|
+
instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
|
|
764
|
+
toolsCount: MUXO_MCP_TOOLS.length,
|
|
765
|
+
skillsCount: getMuxoMcpSkills().length,
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (pathname === "/mcp" && req.method === "DELETE") {
|
|
770
|
+
res.writeHead(204, {
|
|
771
|
+
"Access-Control-Allow-Origin": "*",
|
|
772
|
+
});
|
|
773
|
+
res.end();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
449
776
|
if (pathname === "/mcp" && req.method === "POST") {
|
|
450
777
|
const ctx = resolveContext(req);
|
|
778
|
+
const sessionId = String(req.headers["mcp-session-id"] || "") ||
|
|
779
|
+
`mcp_sess_${ctx.workspace.id}`;
|
|
451
780
|
const raw = await readBody(req);
|
|
452
|
-
const
|
|
781
|
+
const parsed = JSON.parse(raw || "{}");
|
|
782
|
+
if (Array.isArray(parsed)) {
|
|
783
|
+
const responses = (await Promise.all(parsed.map((item) => handleJsonRpcMcpRequest(engine, ctx, (item || {}))))).filter((r) => r !== null);
|
|
784
|
+
sendJson(res, 200, responses, { "Mcp-Session-Id": sessionId });
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const rpc = parsed;
|
|
453
788
|
const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
|
|
454
789
|
if (!response) {
|
|
455
|
-
res.writeHead(202
|
|
790
|
+
res.writeHead(202, {
|
|
791
|
+
"Access-Control-Allow-Origin": "*",
|
|
792
|
+
"Mcp-Session-Id": sessionId,
|
|
793
|
+
});
|
|
456
794
|
res.end();
|
|
457
795
|
return;
|
|
458
796
|
}
|
|
459
|
-
sendJson(res, 200, response);
|
|
797
|
+
sendJson(res, 200, response, { "Mcp-Session-Id": sessionId });
|
|
460
798
|
return;
|
|
461
799
|
}
|
|
462
800
|
// Auth Endpoints
|
|
@@ -500,6 +838,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
500
838
|
landingUrl: `${config.baseUrl}/landing`,
|
|
501
839
|
},
|
|
502
840
|
apiKeys: store.listApiKeys(ctx.workspace.id),
|
|
841
|
+
byokKeys: store.listByokKeys(ctx.workspace.id),
|
|
503
842
|
defaultApiKey: bootstrap.defaultApiKey || null,
|
|
504
843
|
connections: store.listConnections(ctx.workspace.id),
|
|
505
844
|
resources: store.listResources(ctx.workspace.id),
|
|
@@ -619,8 +958,16 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
619
958
|
}
|
|
620
959
|
if (pathname === "/api/v1/connections/wait" && req.method === "POST") {
|
|
621
960
|
const body = JSON.parse((await readBody(req)) || "{}");
|
|
961
|
+
const provider = String(body.provider || "");
|
|
962
|
+
if (body.simulateCallback === true || body.simulate_callback === true) {
|
|
963
|
+
await engine.handleOAuthCallback({
|
|
964
|
+
workspaceId: ctx.workspace.id,
|
|
965
|
+
provider,
|
|
966
|
+
status: "success",
|
|
967
|
+
});
|
|
968
|
+
}
|
|
622
969
|
const result = await engine.waitForConnection(ctx, {
|
|
623
|
-
provider
|
|
970
|
+
provider,
|
|
624
971
|
timeoutSeconds: body.timeoutSeconds !== undefined
|
|
625
972
|
? Number(body.timeoutSeconds)
|
|
626
973
|
: body.timeout_seconds !== undefined
|
|
@@ -712,7 +1059,91 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
712
1059
|
sendJson(res, 200, { ok: true, ...verified });
|
|
713
1060
|
return;
|
|
714
1061
|
}
|
|
1062
|
+
// BYOK (Bring Your Own Key) Encrypted Vault Management
|
|
1063
|
+
if (pathname === "/api/v1/byok" && req.method === "GET") {
|
|
1064
|
+
sendJson(res, 200, {
|
|
1065
|
+
ok: true,
|
|
1066
|
+
workspaceId: ctx.workspace.id,
|
|
1067
|
+
keys: store.listByokKeys(ctx.workspace.id),
|
|
1068
|
+
});
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (pathname === "/api/v1/byok" && req.method === "POST") {
|
|
1072
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1073
|
+
const provider = String(body.provider || "").trim();
|
|
1074
|
+
const secretKey = String(body.secretKey || body.apiKey || body.key || "").trim();
|
|
1075
|
+
if (!provider || !secretKey) {
|
|
1076
|
+
sendJson(res, 400, {
|
|
1077
|
+
ok: false,
|
|
1078
|
+
error: "Both 'provider' and 'secretKey' are required",
|
|
1079
|
+
});
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const record = store.setByokKey(ctx.workspace.id, provider, secretKey);
|
|
1083
|
+
sendJson(res, 201, {
|
|
1084
|
+
ok: true,
|
|
1085
|
+
key: record,
|
|
1086
|
+
keys: store.listByokKeys(ctx.workspace.id),
|
|
1087
|
+
});
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (pathname.startsWith("/api/v1/byok/") && req.method === "DELETE") {
|
|
1091
|
+
const provider = decodeURIComponent(pathname.slice("/api/v1/byok/".length));
|
|
1092
|
+
const deleted = store.deleteByokKey(ctx.workspace.id, provider);
|
|
1093
|
+
sendJson(res, 200, {
|
|
1094
|
+
ok: true,
|
|
1095
|
+
deleted,
|
|
1096
|
+
provider,
|
|
1097
|
+
keys: store.listByokKeys(ctx.workspace.id),
|
|
1098
|
+
});
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
// Natural Language Intent & Governed Execution Endpoint
|
|
1102
|
+
if ((pathname === "/api/v1/do" || pathname === "/v1/do") &&
|
|
1103
|
+
req.method === "POST") {
|
|
1104
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1105
|
+
const prompt = String(body.prompt || body.instruction || body.query || body.command || "").trim();
|
|
1106
|
+
if (!prompt) {
|
|
1107
|
+
sendJson(res, 400, {
|
|
1108
|
+
ok: false,
|
|
1109
|
+
error: "Missing 'prompt' or 'instruction' in request body",
|
|
1110
|
+
});
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const result = await engine.doNaturalLanguage(ctx, {
|
|
1114
|
+
prompt,
|
|
1115
|
+
input: body.input && typeof body.input === "object"
|
|
1116
|
+
? body.input
|
|
1117
|
+
: undefined,
|
|
1118
|
+
autoApprove: body.autoApprove !== false,
|
|
1119
|
+
dryRun: body.dryRun === true,
|
|
1120
|
+
});
|
|
1121
|
+
sendJson(res, 200, result);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
715
1124
|
// Live Stripe Billing, Credit Top-Up & Supplier Settlement Ledger
|
|
1125
|
+
if (pathname === "/api/v1/billing/status" && req.method === "GET") {
|
|
1126
|
+
const ws = store.getWorkspace(ctx.workspace.id);
|
|
1127
|
+
const settlementSummary = store.getSupplierSettlementSummary(ctx.workspace.id);
|
|
1128
|
+
const byokKeys = store.listByokKeys(ctx.workspace.id);
|
|
1129
|
+
sendJson(res, 200, {
|
|
1130
|
+
ok: true,
|
|
1131
|
+
workspaceId: ws.id,
|
|
1132
|
+
workspace: ws,
|
|
1133
|
+
tier: ws.tier,
|
|
1134
|
+
creditsBalance: ws.creditsBalance,
|
|
1135
|
+
creditBalance: ws.creditsBalance,
|
|
1136
|
+
monthlyActionsUsed: ws.monthlyActionsUsed,
|
|
1137
|
+
monthlyActionsLimit: ws.monthlyActionsLimit,
|
|
1138
|
+
stripeConfigured: engine.stripeBilling.isConfigured,
|
|
1139
|
+
stripeCustomerId: ws.stripeCustomerId,
|
|
1140
|
+
stripeSubscriptionId: ws.stripeSubscriptionId,
|
|
1141
|
+
settlementSummary,
|
|
1142
|
+
byokKeys,
|
|
1143
|
+
entries: store.listLedgerEntries(ctx.workspace.id, 25),
|
|
1144
|
+
});
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
716
1147
|
if (pathname === "/api/v1/billing/ledger" && req.method === "GET") {
|
|
717
1148
|
sendJson(res, 200, {
|
|
718
1149
|
ok: true,
|
|
@@ -751,21 +1182,51 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
|
|
|
751
1182
|
});
|
|
752
1183
|
return;
|
|
753
1184
|
}
|
|
1185
|
+
if (pathname === "/api/v1/billing/upgrade" && req.method === "POST") {
|
|
1186
|
+
const body = JSON.parse((await readBody(req)) || "{}");
|
|
1187
|
+
const referenceId = String(body.referenceId || "").trim() ||
|
|
1188
|
+
`upgrade_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1189
|
+
const bonusCredits = body.bonusCredits !== undefined ? Number(body.bonusCredits) : 1000;
|
|
1190
|
+
const updatedWorkspace = store.upgradeWorkspaceToStarter({
|
|
1191
|
+
workspaceId: ctx.workspace.id,
|
|
1192
|
+
stripeCustomerId: ctx.workspace.stripeCustomerId ||
|
|
1193
|
+
`cus_muxo_${ctx.workspace.id.slice(3, 11)}`,
|
|
1194
|
+
stripeSubscriptionId: ctx.workspace.stripeSubscriptionId ||
|
|
1195
|
+
`sub_muxo_${referenceId.slice(-10)}`,
|
|
1196
|
+
referenceId,
|
|
1197
|
+
bonusCredits,
|
|
1198
|
+
});
|
|
1199
|
+
sendJson(res, 200, {
|
|
1200
|
+
ok: true,
|
|
1201
|
+
tier: updatedWorkspace.tier,
|
|
1202
|
+
creditsBalance: updatedWorkspace.creditsBalance,
|
|
1203
|
+
creditBalance: updatedWorkspace.creditsBalance,
|
|
1204
|
+
workspace: updatedWorkspace,
|
|
1205
|
+
settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
|
|
1206
|
+
});
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
754
1209
|
if (pathname === "/api/v1/billing/checkout" && req.method === "POST") {
|
|
755
1210
|
const body = JSON.parse((await readBody(req)) || "{}");
|
|
756
1211
|
const kind = body.kind === "credit_topup" ? "credit_topup" : "starter_subscription";
|
|
1212
|
+
const credits = body.credits ? Number(body.credits) : 1000;
|
|
757
1213
|
const session = await engine.stripeBilling.createCheckoutSession({
|
|
758
1214
|
workspace: ctx.workspace,
|
|
759
1215
|
customerEmail: ctx.user.email,
|
|
760
1216
|
kind,
|
|
761
|
-
|
|
1217
|
+
credits,
|
|
1218
|
+
returnBaseUrl: body.returnBaseUrl ? String(body.returnBaseUrl) : config.baseUrl,
|
|
762
1219
|
});
|
|
763
1220
|
sendJson(res, 200, { ok: true, ...session });
|
|
764
1221
|
return;
|
|
765
1222
|
}
|
|
766
1223
|
if (pathname === "/api/v1/billing/verify-session" && req.method === "POST") {
|
|
767
1224
|
const body = JSON.parse((await readBody(req)) || "{}");
|
|
768
|
-
const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(body.sessionId || "")
|
|
1225
|
+
const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(body.sessionId || ""), ctx.workspace.id, body.kind === "credit_topup"
|
|
1226
|
+
? "credit_topup"
|
|
1227
|
+
: body.kind === "starter_subscription"
|
|
1228
|
+
? "starter_subscription"
|
|
1229
|
+
: undefined);
|
|
769
1230
|
sendJson(res, 200, { ok: true, ...verified });
|
|
770
1231
|
return;
|
|
771
1232
|
}
|