Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.10.2"
".": "1.10.3"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 118
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-439b1a08248ef3bc7457c7b0a9a6218149732535a9f0dd541501fc35e2a3f8c2.yml
openapi_spec_hash: 35b12a086d98484417ce3d2543c47929
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-132ed160716591bdcd9231c00da8c506d9451a5486b165fc27b2a01d93202082.yml
openapi_spec_hash: c2b44d9e9cda56e32141a7ea3794bbba
config_hash: cbda3692cb48ab8582a0df1674b9e5c8
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 1.10.3 (2026-02-27)

Full Changelog: [v1.10.2...v1.10.3](https://github.com/runloopai/api-client-ts/compare/v1.10.2...v1.10.3)

### Bug Fixes

* switching to MCP definition to specify a secret name per MCP server ([#7715](https://github.com/runloopai/api-client-ts/issues/7715)) ([ee0078f](https://github.com/runloopai/api-client-ts/commit/ee0078f21eedd7fd04d3d9bc39ee7f14dae3009c))


### Chores

* add mcp example ([#730](https://github.com/runloopai/api-client-ts/issues/730)) ([ed77255](https://github.com/runloopai/api-client-ts/commit/ed77255e95b49fbc815cb9118df73144bb2bd790))
* **internal:** move stringifyQuery implementation to internal function ([aab68c2](https://github.com/runloopai/api-client-ts/commit/aab68c2e4c7ac559d1b36f0ec945602b8fc93c12))


### Documentation

* fix examples.md ([#732](https://github.com/runloopai/api-client-ts/issues/732)) ([ffef871](https://github.com/runloopai/api-client-ts/commit/ffef871a74138c29791c1b834248a5bbc948c0a8))

## 1.10.2 (2026-02-26)

Full Changelog: [v1.10.1...v1.10.2](https://github.com/runloopai/api-client-ts/compare/v1.10.1...v1.10.2)
Expand Down
6 changes: 3 additions & 3 deletions examples/mcp-github-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ async function main() {
resource_size_request: 'SMALL',
keep_alive_time_seconds: 300,
},
mcp: [
{
mcp: {
MCP_SECRET: {
mcp_config: mcpConfig.id,
secret: SECRET_NAME,
},
],
},
});
console.log(` Devbox: ${devbox.id}`);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@runloop/api-client",
"version": "1.10.2",
"version": "1.10.3",
"description": "The official TypeScript library for the Runloop API",
"author": "Runloop <support@runloop.ai>",
"types": "dist/sdk.d.ts",
Expand Down
20 changes: 4 additions & 16 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
APIConnectionTimeoutError,
APIUserAbortError,
} from './error';
import { stringifyQuery } from './internal/utils/query';
import {
kind as shimsKind,
type Readable,
Expand Down Expand Up @@ -542,27 +543,14 @@ export abstract class APIClient {
}

if (typeof query === 'object' && query && !Array.isArray(query)) {
url.search = this.stringifyQuery(query as Record<string, unknown>);
url.search = this.stringifyQuery(query);
}

return url.toString();
}

protected stringifyQuery(query: Record<string, unknown>): string {
return Object.entries(query)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new RunloopError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
.join('&');
protected stringifyQuery(query: object | Record<string, unknown>): string {
return stringifyQuery(query);
}

async fetchWithTimeout(
Expand Down
23 changes: 23 additions & 0 deletions src/internal/utils/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { RunloopError } from '../../error';

/**
* Basic re-implementation of `qs.stringify` for primitive types.
*/
export function stringifyQuery(query: object | Record<string, unknown>) {
return Object.entries(query)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new RunloopError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
.join('&');
}
20 changes: 11 additions & 9 deletions src/resources/devboxes/devboxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -938,10 +938,11 @@ export interface DevboxView {
initiator_type?: 'unknown' | 'api' | 'scenario' | 'scoring_validation';

/**
* [Beta] MCP specifications configured for this devbox. Each spec links an MCP
* config to a secret for MCP server access through the MCP hub.
* [Beta] MCP specifications configured for this devbox. Map key is the environment
* variable name for the MCP token envelope. Each spec links an MCP config to a
* secret for MCP server access through the MCP hub.
*/
mcp_specs?: Array<DevboxView.McpSpec> | null;
mcp_specs?: { [key: string]: DevboxView.McpSpecs } | null;

/**
* The name of the Devbox.
Expand Down Expand Up @@ -1010,7 +1011,7 @@ export namespace DevboxView {
secret_id: string;
}

export interface McpSpec {
export interface McpSpecs {
/**
* The ID of the MCP config (e.g., mcp_123abc).
*/
Expand Down Expand Up @@ -1150,12 +1151,13 @@ export interface DevboxCreateParams {
launch_parameters?: Shared.LaunchParameters | null;

/**
* [Beta] (Optional) MCP specifications for MCP server access. Each spec links an
* MCP config to a secret. The devbox will receive environment variables
* (RL_MCP_URL, RL_MCP_TOKEN) for accessing MCP servers through the MCP hub.
* Example: [{'mcp_config': 'github-readonly', 'secret': 'MY_GITHUB_TOKEN'}]
* [Beta] (Optional) MCP specifications for MCP server access. Map key is the
* environment variable name for the MCP token envelope. Each spec links an MCP
* config to a secret. The devbox will also receive RL_MCP_URL for the MCP hub
* endpoint. Example: {'MCP_SECRET': {'mcp_config': 'github-readonly', 'secret':
* 'MY_GITHUB_TOKEN'}}
*/
mcp?: Array<DevboxCreateParams.Mcp> | null;
mcp?: { [key: string]: DevboxCreateParams.Mcp } | null;

/**
* User defined metadata to attach to the devbox for organization.
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '1.10.2'; // x-release-please-version
export const VERSION = '1.10.3'; // x-release-please-version
2 changes: 1 addition & 1 deletion tests/api-resources/devboxes/devboxes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('resource devboxes', () => {
resource_size_request: 'X_SMALL',
user_parameters: { uid: 0, username: 'username' },
},
mcp: [{ mcp_config: 'mcp_config', secret: 'secret' }],
mcp: { foo: { mcp_config: 'mcp_config', secret: 'secret' } },
metadata: { foo: 'string' },
mounts: [
{
Expand Down
18 changes: 9 additions & 9 deletions tests/smoketests/object-oriented/mcp-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,12 @@ describe('smoketest: object-oriented mcp config', () => {
resource_size_request: 'X_SMALL',
keep_alive_time_seconds: 300,
},
mcp: [
{
mcp: {
MCP_SECRET: {
mcp_config: mcpConfig.id,
secret: testSecretName,
},
],
},
});

const urlResult = await devbox.cmd.exec('echo $RL_MCP_URL');
Expand Down Expand Up @@ -681,12 +681,12 @@ describe('smoketest: object-oriented mcp config', () => {
resource_size_request: 'X_SMALL',
keep_alive_time_seconds: 60,
},
mcp: [
{
mcp: {
MCP_SECRET: {
mcp_config: mcpConfigName,
secret: testSecretName,
},
],
},
});

expect(devbox).toBeDefined();
Expand Down Expand Up @@ -759,12 +759,12 @@ describe('smoketest: object-oriented mcp config', () => {
resource_size_request: 'X_SMALL',
keep_alive_time_seconds: 60,
},
mcp: [
{
mcp: {
MCP_SECRET: {
mcp_config: mcpConfig.id,
secret: mcpSecretName,
},
],
},
gateways: {
MY_API: {
gateway: gatewayConfig.id,
Expand Down
6 changes: 2 additions & 4 deletions tests/stringifyQuery.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Runloop } from '@runloop/api-client';

const { stringifyQuery } = Runloop.prototype as any;
import { stringifyQuery } from '@runloop/api-client/internal/utils/query';

describe(stringifyQuery, () => {
for (const [input, expected] of [
Expand All @@ -15,7 +13,7 @@ describe(stringifyQuery, () => {
'e=f',
)}=${encodeURIComponent('g&h')}`,
],
]) {
] as const) {
it(`${JSON.stringify(input)} -> ${expected}`, () => {
expect(stringifyQuery(input)).toEqual(expected);
});
Expand Down
Loading