Unified · Split
| @@ -1,4 +1,5 @@ | |||
| 1 | import { Sandbox } from "@vercel/sandbox"; | 1 | import { Sandbox } from "@vercel/sandbox"; |
| 2 | import { generateKeyPairSync } from "node:crypto"; | ||
| 2 | 3 | ||
| 3 | type CommandResult = { | 4 | type CommandResult = { |
| 4 | exitCode: number; | 5 | exitCode: number; |
| @@ -32,6 +33,52 @@ function sandboxCredentials(): { token: string; teamId: string; projectId: strin · 27 unmodified lines | |||
| 32 | return {}; | 33 | return {}; |
| 33 | } | 34 | } |
| 34 | 35 | ||
| 36 | function normalizePemForEnv(value: string): string { | ||
| 37 | return value.replace(/\r\n/g, "\n").replace(/\n/g, "\\n").trim(); | ||
| 38 | } | ||
| 39 | |||
| 40 | function resolveAnonymousAuthEnv(): { | ||
| 41 | privateKeyPem: string; | ||
| 42 | publicKeyPem: string; | ||
| 43 | apiKeySecret: string; | ||
| 44 | generated: boolean; | ||
| 45 | } { | ||
| 46 | const privateFromEnv = process.env.ANONYMOUS_AUTH_PRIVATE_KEY_PEM?.trim(); | ||
| 47 | const publicFromEnv = process.env.ANONYMOUS_AUTH_PUBLIC_KEY_PEM?.trim(); | ||
| 48 | const apiKeyFromEnv = process.env.MCP_API_KEY_SECRET?.trim(); | ||
| 49 | |||
| 50 | if (privateFromEnv && publicFromEnv) { | ||
| 51 | return { | ||
| 52 | privateKeyPem: normalizePemForEnv(privateFromEnv), | ||
| 53 | publicKeyPem: normalizePemForEnv(publicFromEnv), | ||
| 54 | apiKeySecret: apiKeyFromEnv && apiKeyFromEnv.length > 0 ? apiKeyFromEnv : normalizePemForEnv(privateFromEnv), | ||
| 55 | generated: false, | ||
| 56 | }; | ||
| 57 | } | ||
| 58 | |||
| 59 | const keyPair = generateKeyPairSync("ec", { | ||
| 60 | namedCurve: "P-256", | ||
| 61 | privateKeyEncoding: { | ||
| 62 | type: "pkcs8", | ||
| 63 | format: "pem", | ||
| 64 | }, | ||
| 65 | publicKeyEncoding: { | ||
| 66 | type: "spki", | ||
| 67 | format: "pem", | ||
| 68 | }, | ||
| 69 | }); | ||
| 70 | |||
| 71 | const privateKeyPem = normalizePemForEnv(keyPair.privateKey); | ||
| 72 | const publicKeyPem = normalizePemForEnv(keyPair.publicKey); | ||
| 73 | |||
| 74 | return { | ||
| 75 | privateKeyPem, | ||
| 76 | publicKeyPem, | ||
| 77 | apiKeySecret: apiKeyFromEnv && apiKeyFromEnv.length > 0 ? apiKeyFromEnv : privateKeyPem, | ||
| 78 | generated: true, | ||
| 79 | }; | ||
| 80 | } | ||
| 81 | |||
| 35 | async function runSandboxBash( | 82 | async function runSandboxBash( |
| 36 | sandbox: Sandbox, | 83 | sandbox: Sandbox, |
| 37 | script: string, | 84 | script: string, |
| @@ -104,6 +151,11 @@ try { · 66 unmodified lines | |||
| 104 | console.log(`[sandbox] created: ${sandbox.sandboxId}`); | 151 | console.log(`[sandbox] created: ${sandbox.sandboxId}`); |
| 105 | console.log("[sandbox] running executor.sh install flow..."); | 152 | console.log("[sandbox] running executor.sh install flow..."); |
| 106 | 153 | ||
| 154 | const anonymousAuthEnv = resolveAnonymousAuthEnv(); | ||
| 155 | if (anonymousAuthEnv.generated) { | ||
| 156 | console.log("[sandbox] generated ephemeral anonymous auth keys for this session"); | ||
| 157 | } | ||
| 158 | |||
| 107 | const install = await runSandboxBash( | 159 | const install = await runSandboxBash( |
| 108 | sandbox, | 160 | sandbox, |
| 109 | [ | 161 | [ |
| @@ -122,6 +174,9 @@ try { · 12 unmodified lines | |||
| 122 | EXECUTOR_BACKEND_PORT: String(backendPort), | 174 | EXECUTOR_BACKEND_PORT: String(backendPort), |
| 123 | EXECUTOR_BACKEND_SITE_PORT: String(sitePort), | 175 | EXECUTOR_BACKEND_SITE_PORT: String(sitePort), |
| 124 | EXECUTOR_WEB_PORT: String(webPort), | 176 | EXECUTOR_WEB_PORT: String(webPort), |
| 177 | ANONYMOUS_AUTH_PRIVATE_KEY_PEM: anonymousAuthEnv.privateKeyPem, | ||
| 178 | ANONYMOUS_AUTH_PUBLIC_KEY_PEM: anonymousAuthEnv.publicKeyPem, | ||
| 179 | MCP_API_KEY_SECRET: anonymousAuthEnv.apiKeySecret, | ||
| 125 | }, | 180 | }, |
| 126 | }, | 181 | }, |
| 127 | ); | 182 | ); |
| @@ -139,6 +194,7 @@ try { · 11 unmodified lines | |||
| 139 | console.log(`Convex Site: ${convexSiteUrl}`); | 194 | console.log(`Convex Site: ${convexSiteUrl}`); |
| 140 | console.log(`MCP (auth): ${convexSiteUrl}/mcp`); | 195 | console.log(`MCP (auth): ${convexSiteUrl}/mcp`); |
| 141 | console.log(`MCP (anonymous): ${convexSiteUrl}/mcp/anonymous`); | 196 | console.log(`MCP (anonymous): ${convexSiteUrl}/mcp/anonymous`); |
| 197 | console.log("Tip: open Web UI and create an anonymous organization to get workspace context + API key."); | ||
| 142 | console.log(""); | 198 | console.log(""); |
| 143 | console.log("The sandbox is left running so you can test from your machine."); | 199 | console.log("The sandbox is left running so you can test from your machine."); |
| 144 | console.log("Stop it from Vercel when you are done, or wait for sandbox timeout."); | 200 | console.log("Stop it from Vercel when you are done, or wait for sandbox timeout."); |