Skip to content

Commit 1203ccb

Browse files
Add MCP experimental feature to beta (#936)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds beta MCP support to JS/Python SDKs with template auto-selection, config POST, MCP URL helpers, and codegen for MCP types/schemas. > > - **SDKs (Beta MCP support)**: > - **JS SDK**: > - Add `McpServer` types and export; extend `Sandbox.betaCreate` with `mcp` option that auto-selects `mcp-gateway-v0`, configures MCP via POST `/config` on port `50005` with retries, and expose `betaGetMcpUrl()`. > - Add `wait(ms)` util; generate MCP types from `spec/mcp-server.json`; new `generate:mcp` script and dev dep `json-schema-to-typescript`. > - **Python SDK**: > - Generate and export `McpServer` (TypedDict); extend sync/async `beta_create` with `mcp` option, auto-select default MCP template, POST MCP config with retries, and add `beta_get_mcp_url()`; define `mcp_port` and `default_mcp_template`. > - Makefile: add `generate-mcp` target; include `datamodel-code-generator` in tooling; update lockfile/pyproject deps. > - **Tooling**: > - Add `spec/mcp-server.json` schema; update codegen Dockerfile to install `datamodel-code-generator`. > - > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cf3f175. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Jonas Scholz <Jonas.Scholz@bbscholz.de>
1 parent d7bc0a3 commit 1203ccb

22 files changed

Lines changed: 5843 additions & 32 deletions

File tree

.changeset/fifty-comics-tease.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@e2b/python-sdk': patch
3+
'e2b': patch
4+
---
5+
6+
Add experimental MCP functionality to beta

apps/web/src/components/Layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function Layout({
2323

2424
return (
2525
<SectionProvider
26-
sections={relativePathname ? allSections[relativePathname] ?? [] : []}
26+
sections={relativePathname ? (allSections[relativePathname] ?? []) : []}
2727
>
2828
<div
2929
className={clsx('h-[100vh] w-full', {

codegen.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ENV PATH="/go/bin:${PATH}"
3434

3535
# Install Python deps (e2b-openapi-python-client is patched version to fix issue with explode)
3636
# https://github.com/openapi-generators/openapi-python-client/pull/1296
37-
RUN pip install black==23.7.0 pyyaml==6.0.2 e2b-openapi-python-client==0.26.2
37+
RUN pip install black==23.7.0 pyyaml==6.0.2 e2b-openapi-python-client==0.26.2 datamodel-code-generator==0.34.0
3838

3939
# Install Node.js and npm
4040
RUN apt-get update && \
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as sdk from 'e2b'
22

33
export function sortTemplatesAliases<
4-
E extends sdk.components['schemas']['Template']['aliases']
4+
E extends sdk.components['schemas']['Template']['aliases'],
55
>(aliases: E) {
66
aliases?.sort()
77
}

packages/js-sdk/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"generate:api": "python ./../../spec/remove_extra_tags.py sandboxes templates auth && openapi-typescript ../../spec/openapi_generated.yml -x api_key --array-length --alphabetize --output src/api/schema.gen.ts",
3333
"generate:envd": "cd ../../spec/envd && buf generate --template buf-js.gen.yaml\n",
3434
"generate:envd-api": "openapi-typescript ../../spec/envd/envd.yaml -x api_key --array-length --alphabetize --output src/envd/schema.gen.ts",
35+
"generate:mcp": "json2ts -i ./../../spec/mcp-server.json -o src/sandbox/mcp.d.ts --unreachableDefinitions --style.singleQuote --no-style.semi",
3536
"generate-ref": "./scripts/generate_sdk_ref.sh",
3637
"check-deps": "knip",
3738
"update-deps": "ncu -u && pnpm i",
@@ -53,6 +54,7 @@
5354
"@vitest/browser": "^3.1.1",
5455
"dotenv": "^16.4.5",
5556
"eslint": "^8.57.1",
57+
"json-schema-to-typescript": "^15.0.4",
5658
"knip": "^5.43.6",
5759
"npm-check-updates": "^16.14.20",
5860
"npm-run-all": "^4.1.5",
@@ -102,4 +104,4 @@
102104
"browserslist": [
103105
"defaults"
104106
]
105-
}
107+
}

packages/js-sdk/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export type {
4646
SandboxPaginator,
4747
} from './sandbox/sandboxApi'
4848

49+
export type { McpServer } from './sandbox/mcp'
50+
4951
export type {
5052
ProcessInfo,
5153
CommandRequestOpts,

packages/js-sdk/src/sandbox/index.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '../connectionConfig'
1010
import { EnvdApiClient, handleEnvdApiError } from '../envd/api'
1111
import { createRpcLogger } from '../logs'
12+
import { wait } from '../utils'
1213
import { Commands, Pty } from './commands'
1314
import { Filesystem } from './filesystem'
1415
import {
@@ -64,6 +65,7 @@ export interface SandboxUrlOpts {
6465
*/
6566
export class Sandbox extends SandboxApi {
6667
protected static readonly defaultTemplate: string = 'base'
68+
protected static readonly defaultMcpTemplate: string = 'mcp-gateway-v0'
6769
protected static readonly defaultSandboxTimeoutMs = DEFAULT_SANDBOX_TIMEOUT_MS
6870

6971
/**
@@ -90,6 +92,7 @@ export class Sandbox extends SandboxApi {
9092
readonly sandboxDomain: string
9193

9294
protected readonly envdPort = 49983
95+
protected readonly mcpPort = 50005
9396

9497
protected readonly connectionConfig: ConnectionConfig
9598
protected readonly envdAccessToken?: string
@@ -301,8 +304,16 @@ export class Sandbox extends SandboxApi {
301304
): Promise<InstanceType<S>> {
302305
const { template, sandboxOpts } =
303306
typeof templateOrOpts === 'string'
304-
? { template: templateOrOpts, sandboxOpts: opts }
305-
: { template: this.defaultTemplate, sandboxOpts: templateOrOpts }
307+
? {
308+
template: templateOrOpts,
309+
sandboxOpts: opts,
310+
}
311+
: {
312+
template: templateOrOpts?.mcp
313+
? this.defaultMcpTemplate
314+
: this.defaultTemplate,
315+
sandboxOpts: templateOrOpts,
316+
}
306317

307318
const config = new ConnectionConfig(sandboxOpts)
308319
if (config.debug) {
@@ -313,13 +324,54 @@ export class Sandbox extends SandboxApi {
313324
}) as InstanceType<S>
314325
}
315326

316-
const sandbox = await SandboxApi.createSandbox(
327+
const sandboxInfo = await SandboxApi.createSandbox(
317328
template,
318329
sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
319330
sandboxOpts
320331
)
321332

322-
return new this({ ...sandbox, ...config }) as InstanceType<S>
333+
const sandbox = new this({ ...sandboxInfo, ...config }) as InstanceType<S>
334+
335+
if (sandboxOpts?.mcp) {
336+
const mcpConfigUrl = `${
337+
config.debug ? 'http' : 'https'
338+
}://${sandbox.getHost(sandbox.mcpPort)}/config`
339+
340+
const signal = config.getSignal()
341+
342+
let mcpConfigured = false
343+
344+
// TODO: The MCP config seems to succeed on first attempt, but we are keeping the retry logic here for now.
345+
for (let i = 0; i < 5; i++) {
346+
try {
347+
const res = await fetch(mcpConfigUrl, {
348+
method: 'POST',
349+
body: JSON.stringify(sandboxOpts?.mcp),
350+
signal,
351+
})
352+
353+
if (res.ok) {
354+
mcpConfigured = true
355+
356+
break
357+
}
358+
} catch (e) {
359+
config.logger?.warn?.(`Failed to configure MCP server: ${e}`)
360+
}
361+
362+
await wait(250)
363+
}
364+
365+
if (!mcpConfigured) {
366+
await sandbox.kill()
367+
368+
throw new SandboxError(
369+
`Failed to configure MCP server. The sandbox template '${template}' might not be configured with MCP gateway inside.`
370+
)
371+
}
372+
}
373+
374+
return sandbox
323375
}
324376

325377
/**
@@ -519,6 +571,17 @@ export class Sandbox extends SandboxApi {
519571
return await SandboxApi.betaPause(this.sandboxId, opts)
520572
}
521573

574+
/**
575+
* @beta This feature is in beta and may change in the future.
576+
*
577+
* Get the MCP URL for the sandbox.
578+
*
579+
* @returns MCP URL for the sandbox.
580+
*/
581+
betaGetMcpUrl(): string {
582+
return `https://${this.getHost(this.mcpPort)}/mcp`
583+
}
584+
522585
/**
523586
* Get the URL to upload a file to the sandbox.
524587
*

0 commit comments

Comments
 (0)