diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 35c30adcf..81d2de2d2 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.30.0"
+ ".": "1.31.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 75c1b7196..575b1fc60 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 121
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-f8c19bf9329a554bedfbb8678d6d3cb8881a285d27b3d8ba078e2a573b597a17.yml
-openapi_spec_hash: 0c3d490050a9bbaa5cb7378bf55e8f37
-config_hash: 7afcebea2dd638ed0cf0d5367f820f73
+configured_endpoints: 123
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-fe3514739556af3fb20c1e973911deffbaf054eb8c6af21491a0aed338dbbf90.yml
+openapi_spec_hash: e0c6980655d2614c9951c9f231f99cbc
+config_hash: e9fbd57cd90f835db6f3a2b0710b9dcf
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7ee627d9..d9b36f652 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 1.31.0 (2026-08-24)
+
+Full Changelog: [v1.30.0...v1.31.0](https://github.com/runloopai/api-client-python/compare/v1.30.0...v1.31.0)
+
+### Features
+
+* **mux:** mint agent gateway tokens for running devboxes ([#10872](https://github.com/runloopai/api-client-python/issues/10872)) ([b7763ba](https://github.com/runloopai/api-client-python/commit/b7763baa74ba6cabc3b061e606237154e871ef9d))
+* **mux:** mint MCP tokens for running devboxes ([#10871](https://github.com/runloopai/api-client-python/issues/10871)) ([a02b75f](https://github.com/runloopai/api-client-python/commit/a02b75f9c6df9e93ba1495b88c4ce0ae7cb95835))
+
+
+### Chores
+
+* configure new SDK language ([db64ef6](https://github.com/runloopai/api-client-python/commit/db64ef64bf7e0354e589ffe036a44ef9c7ba4c67))
+
## 1.30.0 (2026-08-20)
Full Changelog: [v1.29.0...v1.30.0](https://github.com/runloopai/api-client-python/compare/v1.29.0...v1.30.0)
diff --git a/README.md b/README.md
index d4625b271..2c7512c3d 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,15 @@ and offers both synchronous and asynchronous clients powered by [httpx](https://
It is generated with [Stainless](https://www.stainless.com/).
+## MCP Server
+
+Use the Runloop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
+
+[](https://cursor.com/en-US/install-mcp?name=%40runloop%2Fapi-client-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBydW5sb29wL2FwaS1jbGllbnQtbWNwIl0sImVudiI6eyJSVU5MT09QX0FQSV9LRVkiOiJNeSBCZWFyZXIgVG9rZW4ifX0)
+[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40runloop%2Fapi-client-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40runloop%2Fapi-client-mcp%22%5D%2C%22env%22%3A%7B%22RUNLOOP_API_KEY%22%3A%22My%20Bearer%20Token%22%7D%7D)
+
+> Note: You may need to set environment variables in your MCP client.
+
## Documentation
The REST API documentation can be found on
diff --git a/api.md b/api.md
index af9a3c239..80a57f551 100644
--- a/api.md
+++ b/api.md
@@ -214,6 +214,8 @@ from runloop_api_client.types import (
DevboxSnapshotListView,
DevboxSnapshotView,
DevboxView,
+ GatewayTokenView,
+ McpTokenView,
PtyTunnelView,
TunnelView,
DevboxCreateSSHKeyResponse,
@@ -228,6 +230,8 @@ Methods:
- client.devboxes.retrieve(id) -> DevboxView
- client.devboxes.update(id, \*\*params) -> DevboxView
- client.devboxes.list(\*\*params) -> SyncDevboxesCursorIDPage[DevboxView]
+- client.devboxes.create_gateway_token(id, \*\*params) -> GatewayTokenView
+- client.devboxes.create_mcp_token(id, \*\*params) -> McpTokenView
- client.devboxes.create_pty_tunnel(id) -> PtyTunnelView
- client.devboxes.create_ssh_key(id) -> DevboxCreateSSHKeyResponse
- client.devboxes.delete_disk_snapshot(id) -> object
diff --git a/pyproject.toml b/pyproject.toml
index 622d9ec0b..ca20f54f2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
-version = "1.30.0"
+version = "1.31.0"
description = "The official Python library for the runloop API"
dynamic = ["readme"]
license = "MIT"
diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py
index 14101dc3c..6e58ef0b7 100644
--- a/src/runloop_api_client/_version.py
+++ b/src/runloop_api_client/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runloop_api_client"
-__version__ = "1.30.0" # x-release-please-version
+__version__ = "1.31.0" # x-release-please-version
diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py
index de118e0f8..c0a20f10e 100644
--- a/src/runloop_api_client/resources/devboxes/devboxes.py
+++ b/src/runloop_api_client/resources/devboxes/devboxes.py
@@ -31,11 +31,13 @@
devbox_enable_tunnel_params,
devbox_execute_async_params,
devbox_snapshot_disk_params,
+ devbox_create_mcp_token_params,
devbox_wait_for_command_params,
devbox_read_file_contents_params,
devbox_list_disk_snapshots_params,
devbox_snapshot_disk_async_params,
devbox_write_file_contents_params,
+ devbox_create_gateway_token_params,
)
from ..._files import deepcopy_with_paths
from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
@@ -91,7 +93,9 @@
from ...types.devbox_view import DevboxView
from ...types.tunnel_view import TunnelView
from ...lib.wait_for_status import wait_for_status, async_wait_for_status
+from ...types.mcp_token_view import McpTokenView
from ...types.pty_tunnel_view import PtyTunnelView
+from ...types.gateway_token_view import GatewayTokenView
from ...types.shared_params.mount import Mount
from ...types.devbox_snapshot_view import DevboxSnapshotView
from ...types.shared.launch_parameters import LaunchParameters as SharedLaunchParameters
@@ -608,6 +612,126 @@ def list(
model=DevboxView,
)
+ def create_gateway_token(
+ self,
+ id: str,
+ *,
+ gateway: str,
+ secret: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> GatewayTokenView:
+ """
+ Mint a token that lets a running Devbox call an external API through the Runloop
+ agent gateway, using the credential in the supplied secret. The gateway applies
+ the credential to proxied requests, so the real API key is never exposed to the
+ Devbox.
+
+ The token is bound to this Devbox and is only accepted for requests that
+ originate from it. Nothing is stored on the Devbox: the token is returned to the
+ caller and is not re-issued when the Devbox is resumed.
+
+ Args:
+ gateway: The gateway config to use. Can be a gateway config ID (gwc_xxx) or name.
+
+ secret: The secret containing the credential. Can be a secret ID or name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/devboxes/{id}/create_gateway_token", id=id),
+ body=maybe_transform(
+ {
+ "gateway": gateway,
+ "secret": secret,
+ },
+ devbox_create_gateway_token_params.DevboxCreateGatewayTokenParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=GatewayTokenView,
+ )
+
+ def create_mcp_token(
+ self,
+ id: str,
+ *,
+ mcp_config: str,
+ secret: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> McpTokenView:
+ """
+ [Beta] Mint a token that lets a running Devbox reach an upstream MCP (Model
+ Context Protocol) server through the Runloop MCP hub, using the credential in
+ the supplied secret. Tool access is limited to the MCP config's allowed_tools,
+ and the credential itself is never exposed to the Devbox.
+
+ The token is bound to this Devbox and is only accepted for requests that
+ originate from it. Nothing is stored on the Devbox: the token is returned to the
+ caller and is not re-issued when the Devbox is resumed.
+
+ Args:
+ mcp_config: The MCP config to use. Can be an MCP config ID (mcp_xxx) or name.
+
+ secret: The secret containing the MCP server credential. Can be a secret ID or name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/devboxes/{id}/create_mcp_token", id=id),
+ body=maybe_transform(
+ {
+ "mcp_config": mcp_config,
+ "secret": secret,
+ },
+ devbox_create_mcp_token_params.DevboxCreateMcpTokenParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=McpTokenView,
+ )
+
def create_pty_tunnel(
self,
id: str,
@@ -2324,6 +2448,126 @@ def list(
model=DevboxView,
)
+ async def create_gateway_token(
+ self,
+ id: str,
+ *,
+ gateway: str,
+ secret: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> GatewayTokenView:
+ """
+ Mint a token that lets a running Devbox call an external API through the Runloop
+ agent gateway, using the credential in the supplied secret. The gateway applies
+ the credential to proxied requests, so the real API key is never exposed to the
+ Devbox.
+
+ The token is bound to this Devbox and is only accepted for requests that
+ originate from it. Nothing is stored on the Devbox: the token is returned to the
+ caller and is not re-issued when the Devbox is resumed.
+
+ Args:
+ gateway: The gateway config to use. Can be a gateway config ID (gwc_xxx) or name.
+
+ secret: The secret containing the credential. Can be a secret ID or name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/devboxes/{id}/create_gateway_token", id=id),
+ body=await async_maybe_transform(
+ {
+ "gateway": gateway,
+ "secret": secret,
+ },
+ devbox_create_gateway_token_params.DevboxCreateGatewayTokenParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=GatewayTokenView,
+ )
+
+ async def create_mcp_token(
+ self,
+ id: str,
+ *,
+ mcp_config: str,
+ secret: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> McpTokenView:
+ """
+ [Beta] Mint a token that lets a running Devbox reach an upstream MCP (Model
+ Context Protocol) server through the Runloop MCP hub, using the credential in
+ the supplied secret. Tool access is limited to the MCP config's allowed_tools,
+ and the credential itself is never exposed to the Devbox.
+
+ The token is bound to this Devbox and is only accepted for requests that
+ originate from it. Nothing is stored on the Devbox: the token is returned to the
+ caller and is not re-issued when the Devbox is resumed.
+
+ Args:
+ mcp_config: The MCP config to use. Can be an MCP config ID (mcp_xxx) or name.
+
+ secret: The secret containing the MCP server credential. Can be a secret ID or name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/devboxes/{id}/create_mcp_token", id=id),
+ body=await async_maybe_transform(
+ {
+ "mcp_config": mcp_config,
+ "secret": secret,
+ },
+ devbox_create_mcp_token_params.DevboxCreateMcpTokenParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=McpTokenView,
+ )
+
async def create_pty_tunnel(
self,
id: str,
@@ -3578,6 +3822,12 @@ def __init__(self, devboxes: DevboxesResource) -> None:
self.list = to_raw_response_wrapper(
devboxes.list,
)
+ self.create_gateway_token = to_raw_response_wrapper(
+ devboxes.create_gateway_token,
+ )
+ self.create_mcp_token = to_raw_response_wrapper(
+ devboxes.create_mcp_token,
+ )
self.create_pty_tunnel = to_raw_response_wrapper(
devboxes.create_pty_tunnel,
)
@@ -3677,6 +3927,12 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
self.list = async_to_raw_response_wrapper(
devboxes.list,
)
+ self.create_gateway_token = async_to_raw_response_wrapper(
+ devboxes.create_gateway_token,
+ )
+ self.create_mcp_token = async_to_raw_response_wrapper(
+ devboxes.create_mcp_token,
+ )
self.create_pty_tunnel = async_to_raw_response_wrapper(
devboxes.create_pty_tunnel,
)
@@ -3776,6 +4032,12 @@ def __init__(self, devboxes: DevboxesResource) -> None:
self.list = to_streamed_response_wrapper(
devboxes.list,
)
+ self.create_gateway_token = to_streamed_response_wrapper(
+ devboxes.create_gateway_token,
+ )
+ self.create_mcp_token = to_streamed_response_wrapper(
+ devboxes.create_mcp_token,
+ )
self.create_pty_tunnel = to_streamed_response_wrapper(
devboxes.create_pty_tunnel,
)
@@ -3875,6 +4137,12 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
self.list = async_to_streamed_response_wrapper(
devboxes.list,
)
+ self.create_gateway_token = async_to_streamed_response_wrapper(
+ devboxes.create_gateway_token,
+ )
+ self.create_mcp_token = async_to_streamed_response_wrapper(
+ devboxes.create_mcp_token,
+ )
self.create_pty_tunnel = async_to_streamed_response_wrapper(
devboxes.create_pty_tunnel,
)
diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py
index d7c17637c..d3ad8c627 100644
--- a/src/runloop_api_client/types/__init__.py
+++ b/src/runloop_api_client/types/__init__.py
@@ -32,6 +32,7 @@
from .axon_list_view import AxonListView as AxonListView
from .benchmark_view import BenchmarkView as BenchmarkView
from .blueprint_view import BlueprintView as BlueprintView
+from .mcp_token_view import McpTokenView as McpTokenView
from .agent_list_view import AgentListView as AgentListView
from .axon_event_view import AxonEventView as AxonEventView
from .mcp_config_view import McpConfigView as McpConfigView
@@ -52,6 +53,7 @@
from .benchmark_job_view import BenchmarkJobView as BenchmarkJobView
from .benchmark_run_view import BenchmarkRunView as BenchmarkRunView
from .devbox_list_params import DevboxListParams as DevboxListParams
+from .gateway_token_view import GatewayTokenView as GatewayTokenView
from .object_list_params import ObjectListParams as ObjectListParams
from .pty_connect_params import PtyConnectParams as PtyConnectParams
from .pty_control_params import PtyControlParams as PtyControlParams
@@ -140,6 +142,7 @@
from .scenario_definition_list_view import ScenarioDefinitionListView as ScenarioDefinitionListView
from .scoring_contract_update_param import ScoringContractUpdateParam as ScoringContractUpdateParam
from .blueprint_build_logs_list_view import BlueprintBuildLogsListView as BlueprintBuildLogsListView
+from .devbox_create_mcp_token_params import DevboxCreateMcpTokenParams as DevboxCreateMcpTokenParams
from .devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse
from .devbox_wait_for_command_params import DevboxWaitForCommandParams as DevboxWaitForCommandParams
from .devbox_read_file_contents_params import DevboxReadFileContentsParams as DevboxReadFileContentsParams
@@ -148,6 +151,7 @@
from .devbox_snapshot_disk_async_params import DevboxSnapshotDiskAsyncParams as DevboxSnapshotDiskAsyncParams
from .devbox_write_file_contents_params import DevboxWriteFileContentsParams as DevboxWriteFileContentsParams
from .devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView as DevboxAsyncExecutionDetailView
+from .devbox_create_gateway_token_params import DevboxCreateGatewayTokenParams as DevboxCreateGatewayTokenParams
from .devbox_read_file_contents_response import DevboxReadFileContentsResponse as DevboxReadFileContentsResponse
from .benchmark_run_list_scenario_runs_params import (
BenchmarkRunListScenarioRunsParams as BenchmarkRunListScenarioRunsParams,
diff --git a/src/runloop_api_client/types/devbox_create_gateway_token_params.py b/src/runloop_api_client/types/devbox_create_gateway_token_params.py
new file mode 100644
index 000000000..c12659221
--- /dev/null
+++ b/src/runloop_api_client/types/devbox_create_gateway_token_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["DevboxCreateGatewayTokenParams"]
+
+
+class DevboxCreateGatewayTokenParams(TypedDict, total=False):
+ gateway: Required[str]
+ """The gateway config to use. Can be a gateway config ID (gwc_xxx) or name."""
+
+ secret: Required[str]
+ """The secret containing the credential. Can be a secret ID or name."""
diff --git a/src/runloop_api_client/types/devbox_create_mcp_token_params.py b/src/runloop_api_client/types/devbox_create_mcp_token_params.py
new file mode 100644
index 000000000..ee7b1b5a8
--- /dev/null
+++ b/src/runloop_api_client/types/devbox_create_mcp_token_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["DevboxCreateMcpTokenParams"]
+
+
+class DevboxCreateMcpTokenParams(TypedDict, total=False):
+ mcp_config: Required[str]
+ """The MCP config to use. Can be an MCP config ID (mcp_xxx) or name."""
+
+ secret: Required[str]
+ """The secret containing the MCP server credential. Can be a secret ID or name."""
diff --git a/src/runloop_api_client/types/gateway_token_view.py b/src/runloop_api_client/types/gateway_token_view.py
new file mode 100644
index 000000000..f9a940d65
--- /dev/null
+++ b/src/runloop_api_client/types/gateway_token_view.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .shared.auth_mechanism import AuthMechanism
+
+__all__ = ["GatewayTokenView"]
+
+
+class GatewayTokenView(BaseModel):
+ token: str
+ """The token to send to the gateway as a Bearer token in the Authorization header.
+
+ Only accepted for requests originating from the bound Devbox.
+ """
+
+ auth_mechanism: AuthMechanism
+ """How the gateway applies the credential to proxied requests."""
+
+ devbox_id: str
+ """The Devbox the token is bound to."""
+
+ endpoint: str
+ """The target API endpoint the gateway proxies to."""
+
+ gateway_config_id: str
+ """The ID of the gateway config the token proxies through."""
+
+ url: str
+ """The gateway URL to send requests to.
+
+ Matches the value of the {prefix}\\__URL environment variable inside the
+ Devbox.
+ """
diff --git a/src/runloop_api_client/types/mcp_token_view.py b/src/runloop_api_client/types/mcp_token_view.py
new file mode 100644
index 000000000..d98c5f5be
--- /dev/null
+++ b/src/runloop_api_client/types/mcp_token_view.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+
+__all__ = ["McpTokenView"]
+
+
+class McpTokenView(BaseModel):
+ token: str
+ """The token to send to the MCP hub as a Bearer token in the Authorization header.
+
+ Only accepted for requests originating from the bound Devbox.
+ """
+
+ allowed_tools: List[str]
+ """Glob patterns for the tools the token permits."""
+
+ devbox_id: str
+ """The Devbox the token is bound to."""
+
+ endpoint: str
+ """The upstream MCP server endpoint the hub proxies to."""
+
+ mcp_config_id: str
+ """The ID of the MCP config the token grants access to."""
+
+ url: str
+ """The MCP hub URL the token authenticates against.
+
+ Matches the RL_MCP_URL environment variable inside the Devbox.
+ """
diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py
index 490448dc4..42c4aad15 100644
--- a/tests/api_resources/test_devboxes.py
+++ b/tests/api_resources/test_devboxes.py
@@ -15,7 +15,9 @@
from runloop_api_client.types import (
DevboxView,
TunnelView,
+ McpTokenView,
PtyTunnelView,
+ GatewayTokenView,
DevboxSnapshotView,
DevboxResourceUsageView,
DevboxExecutionDetailView,
@@ -274,6 +276,98 @@ def test_streaming_response_list(self, client: Runloop) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_create_gateway_token(self, client: Runloop) -> None:
+ devbox = client.devboxes.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ )
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_create_gateway_token(self, client: Runloop) -> None:
+ response = client.devboxes.with_raw_response.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ devbox = response.parse()
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create_gateway_token(self, client: Runloop) -> None:
+ with client.devboxes.with_streaming_response.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ devbox = response.parse()
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create_gateway_token(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.devboxes.with_raw_response.create_gateway_token(
+ id="",
+ gateway="gateway",
+ secret="secret",
+ )
+
+ @parametrize
+ def test_method_create_mcp_token(self, client: Runloop) -> None:
+ devbox = client.devboxes.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_create_mcp_token(self, client: Runloop) -> None:
+ response = client.devboxes.with_raw_response.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ devbox = response.parse()
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create_mcp_token(self, client: Runloop) -> None:
+ with client.devboxes.with_streaming_response.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ devbox = response.parse()
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create_mcp_token(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.devboxes.with_raw_response.create_mcp_token(
+ id="",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+
@parametrize
def test_method_create_pty_tunnel(self, client: Runloop) -> None:
devbox = client.devboxes.create_pty_tunnel(
@@ -2037,6 +2131,98 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None
assert cast(Any, response.is_closed) is True
+ @parametrize
+ async def test_method_create_gateway_token(self, async_client: AsyncRunloop) -> None:
+ devbox = await async_client.devboxes.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ )
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create_gateway_token(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.devboxes.with_raw_response.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ devbox = await response.parse()
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create_gateway_token(self, async_client: AsyncRunloop) -> None:
+ async with async_client.devboxes.with_streaming_response.create_gateway_token(
+ id="id",
+ gateway="gateway",
+ secret="secret",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ devbox = await response.parse()
+ assert_matches_type(GatewayTokenView, devbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create_gateway_token(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.devboxes.with_raw_response.create_gateway_token(
+ id="",
+ gateway="gateway",
+ secret="secret",
+ )
+
+ @parametrize
+ async def test_method_create_mcp_token(self, async_client: AsyncRunloop) -> None:
+ devbox = await async_client.devboxes.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create_mcp_token(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.devboxes.with_raw_response.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ devbox = await response.parse()
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create_mcp_token(self, async_client: AsyncRunloop) -> None:
+ async with async_client.devboxes.with_streaming_response.create_mcp_token(
+ id="id",
+ mcp_config="mcp_config",
+ secret="secret",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ devbox = await response.parse()
+ assert_matches_type(McpTokenView, devbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create_mcp_token(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.devboxes.with_raw_response.create_mcp_token(
+ id="",
+ mcp_config="mcp_config",
+ secret="secret",
+ )
+
@parametrize
async def test_method_create_pty_tunnel(self, async_client: AsyncRunloop) -> None:
devbox = await async_client.devboxes.create_pty_tunnel(