diff --git a/package.json b/package.json index 606f2a3f..27921c2d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "dependencies": { "@js-temporal/polyfill": "^0.5.1", "@modelcontextprotocol/sdk": "^1.26.0", - "@runloop/api-client": "1.10.2", + "@runloop/api-client": "1.10.3", "@types/express": "^5.0.6", "chalk": "^5.6.2", "commander": "^14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c65d6fe..65942573 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.26.0 version: 1.26.0(zod@4.3.6) '@runloop/api-client': - specifier: 1.10.2 - version: 1.10.2 + specifier: 1.10.3 + version: 1.10.3 '@types/express': specifier: ^5.0.6 version: 5.0.6 @@ -689,8 +689,8 @@ packages: '@cfworker/json-schema': optional: true - '@runloop/api-client@1.10.2': - resolution: {integrity: sha512-Ts6umf6e2U8TtnXvNza6OIeSciJkWj7bjkacEv9s/HsLDrUbLSE0NMDDiBUko+6Qfc5S4NSRvsvV68/9JTys8Q==} + '@runloop/api-client@1.10.3': + resolution: {integrity: sha512-LtTgHZP72MOxVQPGbh0rT4VTTc5gAYRHAH6Wksr2aahPSVXMsa9wY+KqRGuNlTdJYf1QGsJXbAFcHA34sfOFrg==} '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -3824,7 +3824,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@runloop/api-client@1.10.2': + '@runloop/api-client@1.10.3': dependencies: '@types/node': 18.19.130 '@types/node-fetch': 2.6.13 diff --git a/src/commands/devbox/create.ts b/src/commands/devbox/create.ts index 8f190134..7377d573 100644 --- a/src/commands/devbox/create.ts +++ b/src/commands/devbox/create.ts @@ -115,25 +115,36 @@ function parseGateways( function parseMcpSpecs( specs: string[], -): Array<{ mcp_config: string; secret: string }> { - return specs.map((spec) => { - const commaIndex = spec.indexOf(","); +): Record { + const result: Record = {}; + for (const spec of specs) { + const eqIndex = spec.indexOf("="); + if (eqIndex === -1) { + throw new Error( + `Invalid MCP spec format: ${spec}. Expected ENV_VAR_NAME=mcp_config_id_or_name,secret_id_or_name`, + ); + } + const envVarName = spec.substring(0, eqIndex); + const valueStr = spec.substring(eqIndex + 1); + + const commaIndex = valueStr.indexOf(","); if (commaIndex === -1) { throw new Error( - `Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`, + `Invalid MCP spec format: ${spec}. Expected ENV_VAR_NAME=mcp_config_id_or_name,secret_id_or_name`, ); } - const mcpConfig = spec.substring(0, commaIndex); - const secret = spec.substring(commaIndex + 1); + const mcpConfig = valueStr.substring(0, commaIndex); + const secret = valueStr.substring(commaIndex + 1); - if (!mcpConfig || !secret) { + if (!envVarName || !mcpConfig || !secret) { throw new Error( - `Invalid MCP spec format: ${spec}. Expected mcp_config_id_or_name,secret_id_or_name`, + `Invalid MCP spec format: ${spec}. Expected ENV_VAR_NAME=mcp_config_id_or_name,secret_id_or_name`, ); } - return { mcp_config: mcpConfig, secret }; - }); + result[envVarName] = { mcp_config: mcpConfig, secret }; + } + return result; } export async function createDevbox(options: CreateOptions = {}) { diff --git a/src/components/DevboxCreatePage.tsx b/src/components/DevboxCreatePage.tsx index 0c3ef9e7..6de5f97f 100644 --- a/src/components/DevboxCreatePage.tsx +++ b/src/components/DevboxCreatePage.tsx @@ -81,6 +81,7 @@ interface GatewaySpec { // MCP configuration for devbox interface McpSpec { + envVarName: string; // environment variable name for the MCP token envelope mcpConfig: string; // MCP config ID or name mcpConfigName: string; // display name mcpConfigEndpoint: string; // endpoint URL @@ -638,7 +639,9 @@ export const DevboxCreatePage = ({ // Attach the configured MCP config to the devbox const handleAttachMcp = React.useCallback(() => { if (!pendingMcpConfig || !pendingMcpSecret) return; + const envVarName = `RL_MCP_${pendingMcpConfig.name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`; const newMcp: McpSpec = { + envVarName, mcpConfig: pendingMcpConfig.id, mcpConfigName: pendingMcpConfig.name, mcpConfigEndpoint: pendingMcpConfig.endpoint, @@ -1087,10 +1090,14 @@ export const DevboxCreatePage = ({ // Add MCP specifications if (formData.mcpConfigs.length > 0) { - createParams.mcp = formData.mcpConfigs.map((m) => ({ - mcp_config: m.mcpConfig, - secret: m.secret, - })); + const mcp: Record = {}; + for (const m of formData.mcpConfigs) { + mcp[m.envVarName] = { + mcp_config: m.mcpConfig, + secret: m.secret, + }; + } + createParams.mcp = mcp; } // Add tunnel configuration if not "none" @@ -2610,8 +2617,9 @@ export const DevboxCreatePage = ({ {formData.mcpConfigs.map((m, idx) => ( - {figures.pointer} Config: {m.mcpConfigName} ( - {m.mcpConfigEndpoint}) | Secret: {m.secretName} + {figures.pointer} ENV: {m.envVarName} | Config:{" "} + {m.mcpConfigName} ({m.mcpConfigEndpoint}) | Secret:{" "} + {m.secretName} ))} @@ -2812,6 +2820,9 @@ export const DevboxCreatePage = ({ + + ENV: {m.envVarName} + Endpoint: {m.mcpConfigEndpoint} diff --git a/src/components/DevboxDetailPage.tsx b/src/components/DevboxDetailPage.tsx index 8bed5719..b37c8796 100644 --- a/src/components/DevboxDetailPage.tsx +++ b/src/components/DevboxDetailPage.tsx @@ -30,9 +30,9 @@ export const DevboxDetailPage = ({ devbox, onBack }: DevboxDetailPageProps) => { >({}); React.useEffect(() => { - if (!devbox.mcp_specs || devbox.mcp_specs.length === 0) return; + if (!devbox.mcp_specs || Object.keys(devbox.mcp_specs).length === 0) return; - devbox.mcp_specs.forEach((spec) => { + for (const [, spec] of Object.entries(devbox.mcp_specs)) { getMcpConfig(spec.mcp_config_id) .then((config) => { setMcpEndpoints((prev) => ({ @@ -41,7 +41,7 @@ export const DevboxDetailPage = ({ devbox, onBack }: DevboxDetailPageProps) => { })); }) .catch(() => {}); - }); + } }, [devbox.mcp_specs]); const [selectedOperationKey, setSelectedOperationKey] = React.useState< string | null @@ -311,11 +311,12 @@ export const DevboxDetailPage = ({ devbox, onBack }: DevboxDetailPageProps) => { } // MCP Specs - if (devbox.mcp_specs && devbox.mcp_specs.length > 0) { - devbox.mcp_specs.forEach((spec, idx) => { + if (devbox.mcp_specs && Object.keys(devbox.mcp_specs).length > 0) { + const entries = Object.entries(devbox.mcp_specs); + entries.forEach(([envVarName, spec]) => { const endpoint = mcpEndpoints[spec.mcp_config_id]; detailFields.push({ - label: `MCP Config${devbox.mcp_specs!.length > 1 ? ` ${idx + 1}` : ""}`, + label: `MCP (${envVarName})`, value: ( {spec.mcp_config_id} diff --git a/src/utils/commands.ts b/src/utils/commands.ts index 4fa30415..8107e6c6 100644 --- a/src/utils/commands.ts +++ b/src/utils/commands.ts @@ -71,7 +71,7 @@ export function createProgram(): Command { ) .option( "--mcp ", - "MCP configurations (format: mcp_config_id_or_name,secret_id_or_name)", + "MCP configurations (format: ENV_VAR_NAME=mcp_config_id_or_name,secret_id_or_name)", ) .option( "-o, --output [format]",