From e6e7afff8ff7ff7c7b650f379296887273f334d8 Mon Sep 17 00:00:00 2001 From: saif-at-scalekit Date: Fri, 9 Oct 2026 07:08:17 +0530 Subject: [PATCH 1/9] Update the Scalekit description. (#238) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72aff428..f2bc1f19 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ -

Official Node.js SDK for Scalekit — the auth stack for agents.
+

Official Node.js SDK for Scalekit.
Authentication, authorization, and tool-calling for human-in-the-loop and autonomous agent flows.

[![npm version](https://img.shields.io/npm/v/@scalekit-sdk/node.svg)](https://www.npmjs.com/package/@scalekit-sdk/node) @@ -21,7 +21,7 @@ Authentication, authorization, and tool-calling for human-in-the-loop and autono --- -This is the official Node.js SDK for [Scalekit](https://scalekit.com), — the auth stack for agents. Build secure AI products faster with authentication for humans (SSO, passwordless, full-stack auth) and agents (MCP/APIs, delegated actions), all unified on one platform. +This is the official Node.js SDK for [Scalekit](https://scalekit.com). Scalekit provides auth and actions on behalf of users, with 500+ connectors and 20,000+ tools. Build secure AI products faster with authentication for humans (SSO, passwordless, full-stack auth) and agents (MCP/APIs, delegated actions), all unified on one platform. This Node.js SDK enables both traditional B2B authentication and cutting-edge agentic workflows. #### Agent-First Features - **Agent Identity** — Agents as first-class actors with human ownership and org context From cd063f85443740b3a509a6ab3c43c6c2a8b472ef Mon Sep 17 00:00:00 2001 From: srinivaskarre-sk <153999054+srinivaskarre-sk@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:01:25 +0530 Subject: [PATCH 2/9] chore(release): add CHANGELOG and release-notes folder (#234) * chore(release): release tool, release.toml and seeded CHANGELOG Vendored from scalekit-sdks-wrapper/tools/release. The matching workflow changes (release-checks.yml, release.yml, build.yml workflow_call) ship in a separate commit because they need a push credential with the GitHub 'workflow' scope. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * chore(release): drive releases from scalekit-sdks-wrapper The release tool and its config now live only in the wrapper (tools/release). This repo keeps CHANGELOG.md and .changes/unreleased/ (release notes waiting for the next release). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * ci(release): approval-gated release workflow - release.yml (workflow_dispatch, version input): validates the version against the source, the tags and CHANGELOG.md; reuses build.yml as CI; builds; waits for the 'release' environment approval; tags with the release app; publishes; creates the GitHub Release from the CHANGELOG; verifies the registry. dry_run rehearses without tagging or publishing. - build.yml: add workflow_call so the release reuses the exact PR CI. Generated by scalekit-sdks-wrapper/tools/release/templates/workflows/generate.py. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * docs: describe how this repo's existing release workflow is started Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * ci(release): keep the existing release workflow Restores .github/workflows as on main: releases keep using the existing workflow (or stay manual), started from scalekit-sdks-wrapper after release.py preflight; every PR already has to pass build.yml. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * docs: describe release notes and the release workflow Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017ANmBCXNdijNmAJ49zFRPv * docs(changelog): add the latest release and simplify the header Co-Authored-By: Claude Opus 5.5 * docs(changelog): fix typos and defaults in imported release history Co-Authored-By: Claude Opus 5.5 * docs(changelog): drop leftover contributor handles, reword the import note Co-Authored-By: Claude Opus 5.5 * docs(changelog): fix typos and a broken reference in imported release history Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude --- .changes/unreleased/.gitkeep | 0 .prettierignore | 5 +- CHANGELOG.md | 430 +++++++++++++++++++++++++++++++++++ CLAUDE.md | 20 +- 4 files changed, 444 insertions(+), 11 deletions(-) create mode 100644 .changes/unreleased/.gitkeep create mode 100644 CHANGELOG.md diff --git a/.changes/unreleased/.gitkeep b/.changes/unreleased/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.prettierignore b/.prettierignore index b78022ec..7c7b4c3d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,4 +7,7 @@ CLAUDE.md jest.config.js README.md REFERENCE.md -src/pkg/grpc/ \ No newline at end of file +src/pkg/grpc/ +# Release notes: generated by release tooling +CHANGELOG.md +.changes/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..474ceed6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,430 @@ +# Changelog + +All notable changes to this SDK are documented in this file. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow +[Semantic Versioning](https://semver.org/). + +Sections up to and including 2.19.0 were imported from [GitHub Releases](https://github.com/scalekit-inc/scalekit-sdk-node/releases). Their wording is kept, with small corrections. + +## [2.19.0] - 2026-10-05 + +### Changes + +- feat: accept multiple issuers in token validation (SK-2080) ([#233](https://github.com/scalekit-inc/scalekit-sdk-node/pull/233)) + +## [2.18.0] - 2026-09-29 + +### Changes + +- [SK-1989] Add Virtual MCP and custom connector clients, complete the actions surface ([#227](https://github.com/scalekit-inc/scalekit-sdk-node/pull/227)) + +## [2.17.0] - 2026-09-25 + +### Changes + +- [SK-2043] chore: update proto to v0.1.150.0 (v2.16.2) ([#229](https://github.com/scalekit-inc/scalekit-sdk-node/pull/229)) +- Add getLoginRequestDetails to AuthClient ([#231](https://github.com/scalekit-inc/scalekit-sdk-node/pull/231)) + +## [2.16.1] - 2026-09-21 + +### Changes + +- [SK-2034] fix(deps): resolve all open Dependabot alerts + bump to 2.16.1 ([#228](https://github.com/scalekit-inc/scalekit-sdk-node/pull/228)) + +## [2.16.0] - 2026-09-18 + +### Changes + +- Adding support for Managing ResourceClients ([#226](https://github.com/scalekit-inc/scalekit-sdk-node/pull/226)) + +## [2.15.0] - 2026-09-11 + +### Changes + +- Add listTools method to ActionsClient ([#222](https://github.com/scalekit-inc/scalekit-sdk-node/pull/222)) + +## [2.14.0] - 2026-09-09 + +### Changes + +- [SK-1912] feat: add resources client for listing and revoking user consents ([#221](https://github.com/scalekit-inc/scalekit-sdk-node/pull/221)) +- [SK-1291] feat(tools): add searchTools RPC support ([#217](https://github.com/scalekit-inc/scalekit-sdk-node/pull/217)) + +## [2.13.0] - 2026-09-08 + +### Changes + +- fix: stop transport-level ECONNRESET from surfacing as ScalekitConflictException ([#216](https://github.com/scalekit-inc/scalekit-sdk-node/pull/216)) + +## [2.12.0] - 2026-08-18 + +### Changes + +Scalekit v2.12.0 introduces encrypted-session middleware, mirroring the Python SDK’s middleware. + +This release also includes a security fix by bumping axios to a more secure version. The middleware is framework-agnostic, with adapters for Express and Next.js, and includes features like transparent session refresh, full logout, and configurable local or full logout. + +## [2.11.0] - 2026-07-28 + +### Changes + +- [SK-1339] feat: typed updateLoginUserDetails response + listEventsPaginated ([#210](https://github.com/scalekit-inc/scalekit-sdk-node/pull/210)) +- [SK-1357] Sanitize AxiosError to avoid leaking client_secret on token exchange failure ([#209](https://github.com/scalekit-inc/scalekit-sdk-node/pull/209)) + +## [2.10.0] - 2026-07-17 + +### Changes + +- chore: update proto to v0.1.137.0 ([#206](https://github.com/scalekit-inc/scalekit-sdk-node/pull/206)) + +## [2.9.0] - 2026-07-13 + +### Changes + +- [SK-1227] Add listConnections to Actions and Connection clients ([#201](https://github.com/scalekit-inc/scalekit-sdk-node/pull/201)) +- [SK-1227] Re-export proto Timestamp as AppConnectionTimestamp ([#204](https://github.com/scalekit-inc/scalekit-sdk-node/pull/204)) + +## [2.8.0] - 2026-07-12 + +### Changes + +#### Release Notes + +This release introduces configurable timeouts for management APIs and tool-call APIs. + +- New timeoutMs option — timeout for management API calls (organizations, users, connections, etc.). Default: 20 seconds. +- New toolTimeoutMs option — timeout for tool-call APIs (tools.*, actions.executeTool, actions.request), which can run longer as they call third-party providers. Default: 60 seconds. + +```typescript +const scalekit = new ScalekitClient(envUrl, clientId, clientSecret, { + timeoutMs: 20_000, + toolTimeoutMs: 60_000, +}); +``` + +- Calls that exceed the timeout now throw ScalekitGatewayTimeoutException instead of waiting indefinitely. +- Invalid timeout values (zero or negative) throw an error at client creation. +- actions.request default timeout changed from 30 to 60 seconds; it can still be overridden per call with timeoutMs. + +## [2.7.0] - 2026-07-07 + +### Changes + +- fix(SK-819, SK-821): provider error differentiation, blind retry fix, and upsert credentials ([#200](https://github.com/scalekit-inc/scalekit-sdk-node/pull/200)) + +## [2.6.3] - 2026-06-12 + +### Changes + +- [SK-623] chore(deps): bump 5 low-risk Node deps ([#189](https://github.com/scalekit-inc/scalekit-sdk-node/pull/189)) +- [SK-625] chore(deps): bump axios from 1.13.5 to 1.17.0 ([#190](https://github.com/scalekit-inc/scalekit-sdk-node/pull/190)) +- [SK-626] chore(deps): bump @bufbuild stack (protobuf + buf + protoc-gen-es) ([#192](https://github.com/scalekit-inc/scalekit-sdk-node/pull/192)) +- [SK-628] chore(deps): bump dotenv from 16.6.1 to 17.4.2 ([#193](https://github.com/scalekit-inc/scalekit-sdk-node/pull/193)) +- [SK-630] chore(deps): bump jest + @types/jest from 29 to 30 ([#194](https://github.com/scalekit-inc/scalekit-sdk-node/pull/194)) +- [SK-632] chore(deps): bump @types/node from 20 to 25 ([#195](https://github.com/scalekit-inc/scalekit-sdk-node/pull/195)) +- [SK-633] chore(deps): bump jose from 5 to 6 ([#196](https://github.com/scalekit-inc/scalekit-sdk-node/pull/196)) +- [SK-643] chore(deps): bump typescript from 5 to 6 ([#197](https://github.com/scalekit-inc/scalekit-sdk-node/pull/197)) +- chore: release v2.6.3 ([#198](https://github.com/scalekit-inc/scalekit-sdk-node/pull/198)) + +## [2.6.2] - 2026-05-22 + +### Changes + +- feat: organization session policy SDK methods ([#177](https://github.com/scalekit-inc/scalekit-sdk-node/pull/177)) +- feat: regenerate protos from v0.1.123.0 and add slug/logoUrl to createOrganization ([#184](https://github.com/scalekit-inc/scalekit-sdk-node/pull/184)) +- feat: add new organization and user SDK methods with external_id support ([#180](https://github.com/scalekit-inc/scalekit-sdk-node/pull/180)) +- Release tag 2.6.2 ([#185](https://github.com/scalekit-inc/scalekit-sdk-node/pull/185)) + +## [2.6.0] - 2026-04-24 + +### Changes + +- [SK-2658] feat(token): add updateToken method ([#156](https://github.com/scalekit-inc/scalekit-sdk-node/pull/156)) +- [SK-2664] feat(m2m): add M2MClient (ClientService org-client CRUD) ([#157](https://github.com/scalekit-inc/scalekit-sdk-node/pull/157)) +- [SK-2668] feat(users): add listUserRoles and listUserPermissions ([#158](https://github.com/scalekit-inc/scalekit-sdk-node/pull/158)) +- [SK-2671] feat(roles): add updateDefaultRoles and listDependentRoles ([#159](https://github.com/scalekit-inc/scalekit-sdk-node/pull/159)) +- feat(connected-accounts): add user verification and magic link enhancements ([#161](https://github.com/scalekit-inc/scalekit-sdk-node/pull/161)) +- Release version 20260402 ([#166](https://github.com/scalekit-inc/scalekit-sdk-node/pull/166)) +- build(deps-dev): bump handlebars from 4.7.8 to 4.7.9 ([#163](https://github.com/scalekit-inc/scalekit-sdk-node/pull/163)) +- build(deps-dev): bump picomatch from 2.3.1 to 2.3.2 ([#167](https://github.com/scalekit-inc/scalekit-sdk-node/pull/167)) +- docs: add AGENTKIT.md for AgentKit APIs ([#169](https://github.com/scalekit-inc/scalekit-sdk-node/pull/169)) +- feat(tools): sync proto v0.1.114.0 — is_custom_mcp + Filter fields for custom MCP ([#171](https://github.com/scalekit-inc/scalekit-sdk-node/pull/171)) +- Adding Release Pipeline ([#172](https://github.com/scalekit-inc/scalekit-sdk-node/pull/172)) +- Upgrade npm just before publishing ([#174](https://github.com/scalekit-inc/scalekit-sdk-node/pull/174)) + +## [2.5.0] - 2026-03-19 + +### Changes + +- Adds support for Agent Auth And Connected Accounts. + +## [2.4.0] - 2026-03-10 + +### Changes + +- Updates for ci ([#152](https://github.com/scalekit-inc/scalekit-sdk-node/pull/152)) +- Added Connection API - Add/Delete +- Release v2.4.0 ([#155](https://github.com/scalekit-inc/scalekit-sdk-node/pull/155)) + +## [2.3.0] - 2026-03-10 + +### Changes + +- API Tokens support added. + +## [2.2.2] - 2026-02-23 + +### Changes + +- Bump axios from 1.13.4 to 1.13.5 ([#142](https://github.com/scalekit-inc/scalekit-sdk-node/pull/142)) +- Using speckit: verify accuracy of snippets + make hyperlinks non-relative to base URL ([#145](https://github.com/scalekit-inc/scalekit-sdk-node/pull/145)) +- Bump qs from 6.14.1 to 6.14.2 ([#144](https://github.com/scalekit-inc/scalekit-sdk-node/pull/144)) + +- Bump axios and qs versions ([#147](https://github.com/scalekit-inc/scalekit-sdk-node/pull/147)) + +## [2.2.0] - 2026-02-10 + +### Changes + +- Migrate SDK to Connect/Protobuf v2 and cleanup ([#140](https://github.com/scalekit-inc/scalekit-sdk-node/pull/140)) + +## [2.1.9] - 2026-01-14 + +### Changes + +- added changes for domain type ([#131](https://github.com/scalekit-inc/scalekit-sdk-node/pull/131)) + +#### Release Notes + +#### Version 2.1.9 + +#### New Feature: Domain List API Type Filtering + +Added optional type filtering to `listDomains()` method. Filter domains by `ALLOWED_EMAIL_DOMAIN` or `ORGANIZATION_DOMAIN` type. + +**API:** +```typescript +// List all domains (no filter) +await client.domain.listDomains('org_123456'); + +// Filter by type +await client.domain.listDomains('org_123456', { + domainType: DomainType.ALLOWED_EMAIL_DOMAIN // or 'ORGANIZATION_DOMAIN' +}); +``` + +**Highlights:** +- ✅ Fully backward compatible +- ✅ Supports enum and string values +- ✅ Comprehensive test coverage +- ✅ No migration required + +#### Changes + +**Added:** +- Type filtering parameter in `listDomains()` +- `resolveDomainType()` helper method +- Test coverage for all filtering scenarios + +**Improved:** +- Error messages for invalid domain types +- Type safety for domain parameters + +--- + +#### Migration + +**No migration needed** - existing code works unchanged. The `domainType` parameter is optional. + +--- + +**Documentation:** [Domain API Docs](https://docs.scalekit.com/apis/#tag/domains) + +## [2.1.8] - 2026-01-04 + +### Changes + +- Bump js-yaml from 3.14.1 to 3.14.2 in https://github.com/scalekit-inc/scalekit-sdk-node/pull/127 +- Add WebAuthn support in https://github.com/scalekit-inc/scalekit-sdk-node/pull/130 +- Bump qs from 6.13.0 to 6.14.1 in https://github.com/scalekit-inc/scalekit-sdk-node/pull/134 + +## [2.1.7] - 2025-12-23 + +### Changes + +- Add upsertUserManagementSettings method to OrganizationClient in https://github.com/scalekit-inc/scalekit-sdk-node/pull/128 + +## [2.1.6] - 2025-11-19 + +### Changes + +*Generate proto files to support given_name and family_name in user object in https://github.com/scalekit-inc/scalekit-sdk-node/pull/126 + +## [2.1.5] - 2025-11-13 + +### Changes + +- add verifyInterceptorPayload method in https://github.com/scalekit-inc/scalekit-sdk-node/pull/119 + +## [2.1.4] - 2025-10-13 + +### Changes + +- Add session management sdk methods https://github.com/scalekit-inc/scalekit-sdk-node/pull/111 + +## [2.1.3] - 2025-09-18 + +### Changes + +#### Enhancements +- Add getDomain and deleteDomain methods to DomainClient ([#108](https://github.com/scalekit-inc/scalekit-sdk-node/pull/108)) +- Add role and permission management sdk methods + +## [2.1.1] - 2025-09-02 + +### Changes + +- Add domainType option to createDomain method and updated proto files ([#107](https://github.com/scalekit-inc/scalekit-sdk-node/pull/107)) + +## [2.1.0] - 2025-08-22 + +### Changes + +- Update Error Handling for Node SDK ([#105](https://github.com/scalekit-inc/scalekit-sdk-node/pull/105)) +- Rename sendActivationEmail to sendInvitationEmail and add resendInvite sdk method ([#106](https://github.com/scalekit-inc/scalekit-sdk-node/pull/106)) + +## [2.0.1] - 2025-07-16 + +### Changes + +- Implemented issuer, audience, and scope validation in tokens ([#103](https://github.com/scalekit-inc/scalekit-sdk-node/pull/103)) + +## [2.0.0] - 2025-07-10 + +### Changes + +- Implemented user CRUD methods and refresh token handling in SDK ([#96](https://github.com/scalekit-inc/scalekit-sdk-node/pull/96)) + +## [1.0.14] - 2025-07-03 + +### Changes + +- Upgrade axios version from 1.7.5 to 1.10.0 ([#101](https://github.com/scalekit-inc/scalekit-sdk-node/pull/101)) +- add @bufbuild/protobuf as peerDependencies ([#100](https://github.com/scalekit-inc/scalekit-sdk-node/pull/100)) +- Upgrade undici version to 5.29.0 ([#102](https://github.com/scalekit-inc/scalekit-sdk-node/pull/102)) + +## [1.0.13] - 2025-07-03 + +### Changes + +- made changes to support templateVariables in sendPasswordlessEmail method ([#99](https://github.com/scalekit-inc/scalekit-sdk-node/pull/99)) + +## [1.0.12] - 2025-06-03 + +### Changes + +- Modified the SDK to handle passwordless authentication using magic links. ([#98](https://github.com/scalekit-inc/scalekit-sdk-node/pull/98)) + +## [1.0.11] - 2025-06-02 + +### Changes + +- Add minimum requirements section ([#83](https://github.com/scalekit-inc/scalekit-sdk-node/pull/83)) +- Implemented methods for passwordless in the sdk ([#97](https://github.com/scalekit-inc/scalekit-sdk-node/pull/97)) + +## [1.0.10] - 2025-01-24 + +### Changes + +- Bump undici from 5.28.4 to 5.28.5 ([#71](https://github.com/scalekit-inc/scalekit-sdk-node/pull/71)) + +## [1.0.9] - 2025-01-23 + +### Changes + +- Nodejs minimum version ([#69](https://github.com/scalekit-inc/scalekit-sdk-node/pull/69)) +- [SK-1062] Remove Get and Delete Portal Link Methods ([#67](https://github.com/scalekit-inc/scalekit-sdk-node/pull/67)) + +## [1.0.8] - 2024-11-16 + +### Changes + +- Bump axios from 1.7.3 to 1.7.5 ([#30](https://github.com/scalekit-inc/scalekit-sdk-node/pull/30)) +- Bump @bufbuild/buf from 1.36.0 to 1.42.0 ([#44](https://github.com/scalekit-inc/scalekit-sdk-node/pull/44)) +- Export Scalekit for backward compatibility ([#32](https://github.com/scalekit-inc/scalekit-sdk-node/pull/32)) +- Directory sync changes ([#47](https://github.com/scalekit-inc/scalekit-sdk-node/pull/47)) + +## [1.0.6] - 2024-08-09 + +### Changes + +- Fix connection provider ([#22](https://github.com/scalekit-inc/scalekit-sdk-node/pull/22)) +- IDP Initiated SSO DX v2 ([#23](https://github.com/scalekit-inc/scalekit-sdk-node/pull/23)) + +## [1.0.5] - 2024-07-18 + +### Changes + +- Bump typescript from 5.4.5 to 5.5.3 ([#10](https://github.com/scalekit-inc/scalekit-sdk-node/pull/10)) +- Release/v1.0.5 ([#11](https://github.com/scalekit-inc/scalekit-sdk-node/pull/11)) +- Bump @types/node from 20.12.7 to 20.14.10 ([#9](https://github.com/scalekit-inc/scalekit-sdk-node/pull/9)) +- Bump @types/node from 20.14.10 to 20.14.11 ([#12](https://github.com/scalekit-inc/scalekit-sdk-node/pull/12)) + +## [1.0.4] - 2024-06-24 + +### Changes + +- Improve readme with examples +- Add enable/disable connection +- Add email as default scope + +## [1.0.1] - 2024-06-13 + +### Changes + +- First Release of the official Scalekit Node SDK + +[2.19.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.19.0 +[2.18.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.18.0 +[2.17.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.17.0 +[2.16.1]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.16.1 +[2.16.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.16.0 +[2.15.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.15.0 +[2.14.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.14.0 +[2.13.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.13.0 +[2.12.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.12.0 +[2.11.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.11.0 +[2.10.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.10.0 +[2.9.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.9.0 +[2.8.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.8.0 +[2.7.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.7.0 +[2.6.3]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/2.6.3 +[2.6.2]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/2.6.2 +[2.6.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/2.6.0 +[2.5.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.5.0 +[2.4.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.4.0 +[2.3.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.3.0 +[2.2.2]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.2.2 +[2.2.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.2.0 +[2.1.9]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.9 +[2.1.8]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.8 +[2.1.7]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.7 +[2.1.6]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.6 +[2.1.5]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.5 +[2.1.4]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.4 +[2.1.3]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.3 +[2.1.1]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.1 +[2.1.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.1.0 +[2.0.1]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.0.1 +[2.0.0]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v2.0.0 +[1.0.14]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.14 +[1.0.13]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.13 +[1.0.12]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.12 +[1.0.11]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.11 +[1.0.10]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.10 +[1.0.9]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.9 +[1.0.8]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.8 +[1.0.6]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.6 +[1.0.5]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.5 +[1.0.4]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.4 +[1.0.1]: https://github.com/scalekit-inc/scalekit-sdk-node/releases/tag/v1.0.1 diff --git a/CLAUDE.md b/CLAUDE.md index 69567929..9646dd98 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,15 +28,15 @@ TypeScript 5.x (documentation target; examples mus: Follow standard conventions -## Release - -Package: `@scalekit-sdk/node` on npm. Workflow: `.github/workflows/release.yml`. - -1. Bump the version in `package.json` and `package-lock.json`. Use a minor or patch bump. -2. Review the unreleased changes. If proto or generated API files changed, extra generation steps apply. Those steps are not documented yet. Do not invent them. Ask before you regenerate. -3. Merge the release branch to `main`. -4. Create a git tag that matches the version (`v2.12.0` for `2.12.0`). Draft a GitHub Release for that tag. -5. Publishing does not start on its own. Open the Actions run for the release workflow. Any peer can approve it. The `release` environment gates deploy. -6. After approval, the workflow publishes to npm. +## Release notes and releases + +- Package: `@scalekit-sdk/node` on npm. Workflow: `.github/workflows/release.yml`. +- Every change that users can notice adds a release-notes file under `.changes/unreleased/`. +- Never hand-edit the version (`package.json`, `package-lock.json`) or `CHANGELOG.md` + sections: release tooling bumps the version and renders the CHANGELOG section from those + files. +- Publishing a GitHub Release for a merged version starts the release workflow. It waits for + an approval on the `release` environment from someone other than the person who started + it, then publishes to npm. From ba616675567f6f9d32868b8117efd52e7c499528 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 15:35:01 +0000 Subject: [PATCH 3/9] feat(mcp): mint session tokens for a connection's MCP server Add actions.mcp.createConnectionSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? }). It mints a session token for the MCP server of a single connection (/mcp/v3/connections/) by sending the connection name as keyId and leaving mcpConfigId unset. The expirySeconds validation is shared with createSessionToken, whose behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 --- ...9-153301-mcp-connection-session-token.yaml | 17 +++ REFERENCE.md | 92 +++++++++++++++- lib/mcp.d.ts | 51 +++++++++ lib/mcp.js | 81 ++++++++++++-- lib/mcp.js.map | 2 +- src/mcp.ts | 102 +++++++++++++++--- tests/actions.test.ts | 35 +++++- tests/mcp-session-token.test.ts | 93 ++++++++++++++++ 8 files changed, 448 insertions(+), 25 deletions(-) create mode 100644 .changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml new file mode 100644 index 00000000..3a589cde --- /dev/null +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -0,0 +1,17 @@ +kind: added +body: | + `scalekit.actions.mcp.createConnectionSessionToken(params)` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which exposes all of that connection's tools without an MCP configuration. It takes the same `identifier`, `expirySeconds` and `accessLevel` as `createSessionToken`. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + + ```ts + const session = await scalekit.actions.mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'user_123', + expirySeconds: 900, + accessLevel: 'READ_ONLY', + }); + ``` +time: '2026-10-09T15:33:01.189094+00:00' +custom: + Breaking: 'No' + Ticket: SK-2195 + Migration: '' diff --git a/REFERENCE.md b/REFERENCE.md index 57f11a14..fd62ff21 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -7493,7 +7493,7 @@ console.log(connectedAccount?.status === ConnectorStatus.ACTIVE); ## Virtual MCP Servers -A Virtual MCP server exposes a chosen set of connections and tools over the Model Context Protocol, so any MCP-capable agent can call them. Create one configuration per agent role (not per user), and mint a short-lived session token per user per run. The server URL stays the same; the token carries the user's identity. +A Virtual MCP server exposes a chosen set of connections and tools over the Model Context Protocol, so any MCP-capable agent can call them. Create one configuration per agent role (not per user), and mint a short-lived session token per user per run. The server URL stays the same; the token carries the user's identity. Each AgentKit connection also has its own MCP server with all of its tools; mint tokens for it with `createConnectionSessionToken`. Only the generally available `McpConfig` API is covered. The older `Mcp` and `McpInstance` families are marked PREVIEW and are not exposed. @@ -7990,6 +7990,96 @@ console.log(config?.mcpServerUrl, session.token, session.expiresAt); + + + + +
client.actions.mcp.createConnectionSessionToken(params) -> Promise<CreateMcpSessionTokenResponse> +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Mints a session token for one user against a connection's MCP server. + +Every AgentKit connection has its own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. Use this method for that server and `createSessionToken` for a Virtual MCP server built from a configuration; a token works only on the server it was minted for. + +The user must have a connected account on the connection. Mint a fresh token before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. + +The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection, the user has no active connected account on it, or the identifier or expiry is rejected. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'user_123', + expirySeconds: 900, + accessLevel: 'READ_ONLY', +}); + +// Hand both to your MCP client +const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; +console.log(serverUrl, session.token, session.expiresAt); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**params.connectionName:** `string` - Name of the connection, the same value used as `connectionName` elsewhere in the SDK. An empty string throws before the request is sent. + +
+
+
+
+ +**params.identifier:** `string` - Your application's identifier for the end user whose connected account the token acts as + +
+
+
+
+ +**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server enforces the allowed range and applies its default when omitted. + +
+
+
+
+ +**params.accessLevel?:** `'FULL' | 'READ_ONLY'` - Tools the token can use. `'READ_ONLY'` limits the token to tools annotated read-only (`read_only_hint`): other tools are left out of the tool list and refused when called. `'FULL'`, or omitting it, exposes every tool the connection provides. + +
+
+
+
+ +
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index 5cf14530..2909ce5a 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -135,4 +135,55 @@ export default class McpClient { expirySeconds?: number; accessLevel?: McpSessionTokenAccessLevel; }): Promise; + /** + * Mints a session token for one user against a connection's MCP server. + * + * Every AgentKit connection has its own MCP server at + * `/mcp/v3/connections/`, which exposes all + * of that connection's tools without an MCP configuration. Use this method + * to mint a token for that server; use {@link McpClient.createSessionToken} + * for a Virtual MCP server built from a configuration. A token works only on + * the server it was minted for. + * + * The user must have a connected account on the connection. Mint a fresh + * token before every agent run and never reuse one across runs. Set the + * expiry longer than the run is expected to take. + * + * @param params.connectionName Name of the connection, as shown in the + * dashboard and used as `connectionName` elsewhere in the SDK. + * @param params.identifier Your application's unique identifier for the user + * whose connected account the token acts as. + * @param params.expirySeconds Token lifetime in whole seconds. The server + * enforces the allowed range and applies its default when omitted. + * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it + * to tools annotated read-only: other tools are left out of the tool list + * and refused when called. `'FULL'`, or omitting it, exposes every tool the + * connection provides. + * @returns The session `token` and its `expiresAt` time. + * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a + * positive integer. + * @throws {ScalekitBadRequestException} If the connection is not an AgentKit + * connection, the user has no active connected account on it, or the + * identifier or expiry is rejected. + * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {ScalekitServerException} If a network or server error occurs. + * + * @example + * ```typescript + * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * connectionName: 'gmail', + * identifier: 'user_123', + * expirySeconds: 900, + * accessLevel: 'READ_ONLY', + * }); + * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; + * // Hand serverUrl and session.token to your MCP client + * ``` + */ + createConnectionSessionToken(params: { + connectionName: string; + identifier: string; + expirySeconds?: number; + accessLevel?: McpSessionTokenAccessLevel; + }): Promise; } diff --git a/lib/mcp.js b/lib/mcp.js index 47f30c4c..7730eda1 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -157,20 +157,83 @@ class McpClient { */ createSessionToken(params) { return __awaiter(this, void 0, void 0, function* () { - if (params.expirySeconds !== undefined && - !(Number.isInteger(params.expirySeconds) && params.expirySeconds > 0)) { - // BigInt() below would otherwise throw an unhelpful RangeError. - throw new Error(`expirySeconds must be a positive integer, got ${params.expirySeconds}`); + return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign({ mcpConfigId: params.mcpConfigId, identifier: params.identifier }, expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { + accessLevel: params.accessLevel, + })))); + }); + } + /** + * Mints a session token for one user against a connection's MCP server. + * + * Every AgentKit connection has its own MCP server at + * `/mcp/v3/connections/`, which exposes all + * of that connection's tools without an MCP configuration. Use this method + * to mint a token for that server; use {@link McpClient.createSessionToken} + * for a Virtual MCP server built from a configuration. A token works only on + * the server it was minted for. + * + * The user must have a connected account on the connection. Mint a fresh + * token before every agent run and never reuse one across runs. Set the + * expiry longer than the run is expected to take. + * + * @param params.connectionName Name of the connection, as shown in the + * dashboard and used as `connectionName` elsewhere in the SDK. + * @param params.identifier Your application's unique identifier for the user + * whose connected account the token acts as. + * @param params.expirySeconds Token lifetime in whole seconds. The server + * enforces the allowed range and applies its default when omitted. + * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it + * to tools annotated read-only: other tools are left out of the tool list + * and refused when called. `'FULL'`, or omitting it, exposes every tool the + * connection provides. + * @returns The session `token` and its `expiresAt` time. + * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a + * positive integer. + * @throws {ScalekitBadRequestException} If the connection is not an AgentKit + * connection, the user has no active connected account on it, or the + * identifier or expiry is rejected. + * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {ScalekitServerException} If a network or server error occurs. + * + * @example + * ```typescript + * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * connectionName: 'gmail', + * identifier: 'user_123', + * expirySeconds: 900, + * accessLevel: 'READ_ONLY', + * }); + * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; + * // Hand serverUrl and session.token to your MCP client + * ``` + */ + createConnectionSessionToken(params) { + return __awaiter(this, void 0, void 0, function* () { + if (!params.connectionName) { + // An empty name would otherwise reach the server as "no target set". + throw new Error('connectionName is required'); } - return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign({ mcpConfigId: params.mcpConfigId, identifier: params.identifier }, (params.expirySeconds !== undefined && { - expiry: (0, protobuf_1.create)(wkt_1.DurationSchema, { - seconds: BigInt(params.expirySeconds), - }), - })), (params.accessLevel !== undefined && { + return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign({ keyId: params.connectionName, identifier: params.identifier }, expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { accessLevel: params.accessLevel, })))); }); } } exports.default = McpClient; +/** + * Validates `expirySeconds` and returns the request's `expiry` field, or no + * field when it is omitted so the server applies its default. + */ +function expiryField(expirySeconds) { + if (expirySeconds === undefined) { + return {}; + } + if (!(Number.isInteger(expirySeconds) && expirySeconds > 0)) { + // BigInt() below would otherwise throw an unhelpful RangeError. + throw new Error(`expirySeconds must be a positive integer, got ${expirySeconds}`); + } + return { + expiry: (0, protobuf_1.create)(wkt_1.DurationSchema, { seconds: BigInt(expirySeconds) }), + }; +} //# sourceMappingURL=mcp.js.map \ No newline at end of file diff --git a/lib/mcp.js.map b/lib/mcp.js.map index fd89f46a..a3ed0f54 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/mcp.ts b/src/mcp.ts index 1bed4d49..88a773b1 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -229,25 +229,80 @@ export default class McpClient { expirySeconds?: number; accessLevel?: McpSessionTokenAccessLevel; }): Promise { - if ( - params.expirySeconds !== undefined && - !(Number.isInteger(params.expirySeconds) && params.expirySeconds > 0) - ) { - // BigInt() below would otherwise throw an unhelpful RangeError. - throw new Error( - `expirySeconds must be a positive integer, got ${params.expirySeconds}` - ); - } return this.coreClient.connectExec( this.client.createMcpSessionToken, create(CreateMcpSessionTokenRequestSchema, { mcpConfigId: params.mcpConfigId, identifier: params.identifier, - ...(params.expirySeconds !== undefined && { - expiry: create(DurationSchema, { - seconds: BigInt(params.expirySeconds), - }), + ...expiryField(params.expirySeconds), + ...(params.accessLevel !== undefined && { + accessLevel: params.accessLevel, }), + }) + ); + } + + /** + * Mints a session token for one user against a connection's MCP server. + * + * Every AgentKit connection has its own MCP server at + * `/mcp/v3/connections/`, which exposes all + * of that connection's tools without an MCP configuration. Use this method + * to mint a token for that server; use {@link McpClient.createSessionToken} + * for a Virtual MCP server built from a configuration. A token works only on + * the server it was minted for. + * + * The user must have a connected account on the connection. Mint a fresh + * token before every agent run and never reuse one across runs. Set the + * expiry longer than the run is expected to take. + * + * @param params.connectionName Name of the connection, as shown in the + * dashboard and used as `connectionName` elsewhere in the SDK. + * @param params.identifier Your application's unique identifier for the user + * whose connected account the token acts as. + * @param params.expirySeconds Token lifetime in whole seconds. The server + * enforces the allowed range and applies its default when omitted. + * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it + * to tools annotated read-only: other tools are left out of the tool list + * and refused when called. `'FULL'`, or omitting it, exposes every tool the + * connection provides. + * @returns The session `token` and its `expiresAt` time. + * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a + * positive integer. + * @throws {ScalekitBadRequestException} If the connection is not an AgentKit + * connection, the user has no active connected account on it, or the + * identifier or expiry is rejected. + * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {ScalekitServerException} If a network or server error occurs. + * + * @example + * ```typescript + * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * connectionName: 'gmail', + * identifier: 'user_123', + * expirySeconds: 900, + * accessLevel: 'READ_ONLY', + * }); + * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; + * // Hand serverUrl and session.token to your MCP client + * ``` + */ + async createConnectionSessionToken(params: { + connectionName: string; + identifier: string; + expirySeconds?: number; + accessLevel?: McpSessionTokenAccessLevel; + }): Promise { + if (!params.connectionName) { + // An empty name would otherwise reach the server as "no target set". + throw new Error('connectionName is required'); + } + return this.coreClient.connectExec( + this.client.createMcpSessionToken, + create(CreateMcpSessionTokenRequestSchema, { + keyId: params.connectionName, + identifier: params.identifier, + ...expiryField(params.expirySeconds), ...(params.accessLevel !== undefined && { accessLevel: params.accessLevel, }), @@ -255,3 +310,24 @@ export default class McpClient { ); } } + +/** + * Validates `expirySeconds` and returns the request's `expiry` field, or no + * field when it is omitted so the server applies its default. + */ +function expiryField(expirySeconds: number | undefined): { + expiry?: MessageInitShape; +} { + if (expirySeconds === undefined) { + return {}; + } + if (!(Number.isInteger(expirySeconds) && expirySeconds > 0)) { + // BigInt() below would otherwise throw an unhelpful RangeError. + throw new Error( + `expirySeconds must be a positive integer, got ${expirySeconds}` + ); + } + return { + expiry: create(DurationSchema, { seconds: BigInt(expirySeconds) }), + }; +} diff --git a/tests/actions.test.ts b/tests/actions.test.ts index af3ed457..0ad1185b 100644 --- a/tests/actions.test.ts +++ b/tests/actions.test.ts @@ -1,7 +1,10 @@ import ScalekitClient from '../src/scalekit'; import { create } from '@bufbuild/protobuf'; import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; -import { ScalekitServerException } from '../src/errors'; +import { + ScalekitNotFoundException, + ScalekitServerException, +} from '../src/errors'; import { TestDataGenerator, TestOrganizationManager } from './utils/test-data'; import { AuthorizationDetailsSchema, @@ -185,6 +188,36 @@ describe('Actions', () => { }); }); + describe('mcp.createConnectionSessionToken', () => { + it('should expose createConnectionSessionToken', () => { + expect(typeof client.actions.mcp.createConnectionSessionToken).toBe( + 'function' + ); + }); + + it('should mint a read-only token for the connection MCP server', async () => { + const response = await client.actions.mcp.createConnectionSessionToken({ + connectionName: GMAIL_CONNECTION_NAME, + identifier: GMAIL_IDENTIFIER, + expirySeconds: 300, + accessLevel: 'READ_ONLY', + }); + + expect(typeof response.token).toBe('string'); + expect(response.token.length).toBeGreaterThan(0); + expect(response.expiresAt).toBeDefined(); + }); + + it('should reject an unknown connection with ScalekitNotFoundException', async () => { + await expect( + client.actions.mcp.createConnectionSessionToken({ + connectionName: `missing-connection-${Date.now()}`, + identifier: GMAIL_IDENTIFIER, + }) + ).rejects.toBeInstanceOf(ScalekitNotFoundException); + }); + }); + describe('getAuthorizationLink', () => { it('should call underlying magic link API', async () => { try { diff --git a/tests/mcp-session-token.test.ts b/tests/mcp-session-token.test.ts index babcc44c..7fc71c0e 100644 --- a/tests/mcp-session-token.test.ts +++ b/tests/mcp-session-token.test.ts @@ -2,6 +2,10 @@ * McpClient.createSessionToken must send `accessLevel` only when the caller * sets it. An omitted access level leaves the field empty on the wire, which * the server treats as full access, so existing callers keep their tools. + * + * McpClient.createConnectionSessionToken must target the connection's MCP + * server through `keyId` and never set `mcpConfigId`: the server requires + * exactly one of the two. */ import { describe, it, expect, jest } from '@jest/globals'; import CoreClient from '../src/core'; @@ -55,3 +59,92 @@ describe('McpClient.createSessionToken accessLevel', () => { expect(req.expiry?.seconds).toBe(BigInt(900)); }); }); + +describe('McpClient.createConnectionSessionToken', () => { + it('targets the connection by keyId and leaves mcpConfigId empty', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + const res = await mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'u1', + }); + expect(res.token).toBe('tok'); + expect(createMcpSessionToken).toHaveBeenCalledTimes(1); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.keyId).toBe('gmail'); + expect(req.mcpConfigId).toBe(''); + expect(req.identifier).toBe('u1'); + expect(req.expiry).toBeUndefined(); + expect(req.accessLevel).toBe(''); + }); + + it('forwards expiry and accessLevel', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'u1', + expirySeconds: 900, + accessLevel: 'READ_ONLY', + }); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.keyId).toBe('gmail'); + expect(req.mcpConfigId).toBe(''); + expect(req.expiry?.seconds).toBe(BigInt(900)); + expect(req.accessLevel).toBe('READ_ONLY'); + }); + + it('sends FULL when set explicitly', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'u1', + accessLevel: 'FULL', + }); + expect(createMcpSessionToken.mock.calls[0][0].accessLevel).toBe('FULL'); + }); + + it('rejects an empty connectionName before any request', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await expect( + mcp.createConnectionSessionToken({ connectionName: '', identifier: 'u1' }) + ).rejects.toThrow('connectionName is required'); + expect(createMcpSessionToken).not.toHaveBeenCalled(); + }); + + it.each([0, -1, 900.5, NaN, Infinity])( + 'rejects expirySeconds=%p before any request', + async (expirySeconds) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await expect( + mcp.createConnectionSessionToken({ + connectionName: 'gmail', + identifier: 'u1', + expirySeconds, + }) + ).rejects.toThrow('expirySeconds must be a positive integer'); + expect(createMcpSessionToken).not.toHaveBeenCalled(); + } + ); +}); + +describe('McpClient.createSessionToken expiry validation', () => { + it.each([0, -1, 900.5, NaN])( + 'still rejects expirySeconds=%p before any request', + async (expirySeconds) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await expect( + mcp.createSessionToken({ + mcpConfigId: 'cfg_1', + identifier: 'u1', + expirySeconds, + }) + ).rejects.toThrow('expirySeconds must be a positive integer'); + expect(createMcpSessionToken).not.toHaveBeenCalled(); + } + ); + + it('never sets keyId', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ mcpConfigId: 'cfg_1', identifier: 'u1' }); + expect(createMcpSessionToken.mock.calls[0][0].keyId).toBe(''); + }); +}); From 0cf7cceff5e38b8dbc59ca6902c5d4f4093af0ed Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 15:41:55 +0000 Subject: [PATCH 4/9] docs(mcp): clarify createConnectionSessionToken name, expiry and account rules Document that connectionName is matched to the stored name's casing for the token, the accepted expiry range (60 seconds to 24 hours, default 1 hour), and that a missing connected account is either rejected or results in a pending account, depending on the environment. Co-Authored-By: Claude Opus 5.5 --- REFERENCE.md | 8 ++++---- lib/mcp.d.ts | 27 +++++++++++++++++---------- lib/mcp.js | 27 +++++++++++++++++---------- lib/mcp.js.map | 2 +- src/mcp.ts | 25 ++++++++++++++++--------- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/REFERENCE.md b/REFERENCE.md index fd62ff21..1e47df2a 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -8010,9 +8010,9 @@ Mints a session token for one user against a connection's MCP server. Every AgentKit connection has its own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. Use this method for that server and `createSessionToken` for a Virtual MCP server built from a configuration; a token works only on the server it was minted for. -The user must have a connected account on the connection. Mint a fresh token before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. +The user should have an active connected account on the connection. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. Mint a fresh token before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. -The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection, the user has no active connected account on it, or the identifier or expiry is rejected. +The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection or the identifier or expiry is rejected. @@ -8051,7 +8051,7 @@ console.log(serverUrl, session.token, session.expiresAt);
-**params.connectionName:** `string` - Name of the connection, the same value used as `connectionName` elsewhere in the SDK. An empty string throws before the request is sent. +**params.connectionName:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. Case-sensitive: pass it exactly as stored. The connection is found regardless of case, but the token is bound to the stored name, so a server URL built with different casing refuses it. An empty string throws before the request is sent.
@@ -8065,7 +8065,7 @@ console.log(serverUrl, session.token, session.expiresAt);
-**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server enforces the allowed range and applies its default when omitted. +**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server accepts 60 seconds to 24 hours (86400) and defaults to 1 hour (3600) when omitted.
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index 2909ce5a..f3316f3e 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -145,16 +145,22 @@ export default class McpClient { * for a Virtual MCP server built from a configuration. A token works only on * the server it was minted for. * - * The user must have a connected account on the connection. Mint a fresh - * token before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. - * - * @param params.connectionName Name of the connection, as shown in the - * dashboard and used as `connectionName` elsewhere in the SDK. + * The user should have an active connected account on the connection. When + * they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. Mint a fresh token before + * every agent run and never reuse one across runs. Set the expiry longer + * than the run is expected to take. + * + * @param params.connectionName The connection name: the same value used as + * `connectionName` elsewhere in the SDK. Pass it exactly as stored, + * including case. The connection is found regardless of case, but the token + * is bound to the stored name, so a server URL built with different casing + * refuses it. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. - * @param params.expirySeconds Token lifetime in whole seconds. The server - * enforces the allowed range and applies its default when omitted. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the @@ -163,8 +169,9 @@ export default class McpClient { * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a * positive integer. * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the user has no active connected account on it, or the - * identifier or expiry is rejected. + * connection, the identifier or expiry is rejected, or (in environments that + * do not create pending accounts) the user has no active connected account + * on it. * @throws {ScalekitNotFoundException} If no active connection has that name. * @throws {ScalekitServerException} If a network or server error occurs. * diff --git a/lib/mcp.js b/lib/mcp.js index 7730eda1..6a6e0039 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -172,16 +172,22 @@ class McpClient { * for a Virtual MCP server built from a configuration. A token works only on * the server it was minted for. * - * The user must have a connected account on the connection. Mint a fresh - * token before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. - * - * @param params.connectionName Name of the connection, as shown in the - * dashboard and used as `connectionName` elsewhere in the SDK. + * The user should have an active connected account on the connection. When + * they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. Mint a fresh token before + * every agent run and never reuse one across runs. Set the expiry longer + * than the run is expected to take. + * + * @param params.connectionName The connection name: the same value used as + * `connectionName` elsewhere in the SDK. Pass it exactly as stored, + * including case. The connection is found regardless of case, but the token + * is bound to the stored name, so a server URL built with different casing + * refuses it. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. - * @param params.expirySeconds Token lifetime in whole seconds. The server - * enforces the allowed range and applies its default when omitted. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the @@ -190,8 +196,9 @@ class McpClient { * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a * positive integer. * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the user has no active connected account on it, or the - * identifier or expiry is rejected. + * connection, the identifier or expiry is rejected, or (in environments that + * do not create pending accounts) the user has no active connected account + * on it. * @throws {ScalekitNotFoundException} If no active connection has that name. * @throws {ScalekitServerException} If a network or server error occurs. * diff --git a/lib/mcp.js.map b/lib/mcp.js.map index a3ed0f54..faddb701 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/mcp.ts b/src/mcp.ts index 88a773b1..50a2c781 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -252,16 +252,22 @@ export default class McpClient { * for a Virtual MCP server built from a configuration. A token works only on * the server it was minted for. * - * The user must have a connected account on the connection. Mint a fresh - * token before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. + * The user should have an active connected account on the connection. When + * they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. Mint a fresh token before + * every agent run and never reuse one across runs. Set the expiry longer + * than the run is expected to take. * - * @param params.connectionName Name of the connection, as shown in the - * dashboard and used as `connectionName` elsewhere in the SDK. + * @param params.connectionName The connection name: the same value used as + * `connectionName` elsewhere in the SDK. Pass it exactly as stored, + * including case. The connection is found regardless of case, but the token + * is bound to the stored name, so a server URL built with different casing + * refuses it. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. - * @param params.expirySeconds Token lifetime in whole seconds. The server - * enforces the allowed range and applies its default when omitted. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the @@ -270,8 +276,9 @@ export default class McpClient { * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a * positive integer. * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the user has no active connected account on it, or the - * identifier or expiry is rejected. + * connection, the identifier or expiry is rejected, or (in environments that + * do not create pending accounts) the user has no active connected account + * on it. * @throws {ScalekitNotFoundException} If no active connection has that name. * @throws {ScalekitServerException} If a network or server error occurs. * From bc747a43d548e57a203af7991f63dac0ee33be8e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 15:42:50 +0000 Subject: [PATCH 5/9] docs(mcp): correct connectionName casing note for connection session tokens The connection name is matched without regard to case and the token is issued for the server URL built from the connection's stored name. Only that URL is case-sensitive, so callers connect using the stored name. Co-Authored-By: Claude Opus 5.5 --- ...-20261009-153301-mcp-connection-session-token.yaml | 2 +- REFERENCE.md | 6 +++--- lib/mcp.d.ts | 11 ++++++----- lib/mcp.js | 11 ++++++----- lib/mcp.js.map | 2 +- src/mcp.ts | 11 ++++++----- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml index 3a589cde..2fa2edbb 100644 --- a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -1,6 +1,6 @@ kind: added body: | - `scalekit.actions.mcp.createConnectionSessionToken(params)` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which exposes all of that connection's tools without an MCP configuration. It takes the same `identifier`, `expirySeconds` and `accessLevel` as `createSessionToken`. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + `scalekit.actions.mcp.createConnectionSessionToken(params)` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`, built from the connection's stored name), which exposes all of that connection's tools without an MCP configuration. It takes the same `identifier`, `expirySeconds` and `accessLevel` as `createSessionToken`. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) ```ts const session = await scalekit.actions.mcp.createConnectionSessionToken({ diff --git a/REFERENCE.md b/REFERENCE.md index 1e47df2a..ca88cb4c 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -8008,7 +8008,7 @@ console.log(config?.mcpServerUrl, session.token, session.expiresAt); Mints a session token for one user against a connection's MCP server. -Every AgentKit connection has its own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. Use this method for that server and `createSessionToken` for a Virtual MCP server built from a configuration; a token works only on the server it was minted for. +Every AgentKit connection has its own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. Use this method for that server and `createSessionToken` for a Virtual MCP server built from a configuration; a token works only on the server it was minted for. The user should have an active connected account on the connection. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. Mint a fresh token before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. @@ -8034,7 +8034,7 @@ const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ accessLevel: 'READ_ONLY', }); -// Hand both to your MCP client +// Hand both to your MCP client. The URL path is case-sensitive: use the connection's stored name. const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; console.log(serverUrl, session.token, session.expiresAt); ``` @@ -8051,7 +8051,7 @@ console.log(serverUrl, session.token, session.expiresAt);
-**params.connectionName:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. Case-sensitive: pass it exactly as stored. The connection is found regardless of case, but the token is bound to the stored name, so a server URL built with different casing refuses it. An empty string throws before the request is sent. +**params.connectionName:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. It is matched without regard to case, and the token is always issued for the server URL built from the connection's stored name. That URL is case-sensitive, so connect using the stored name exactly as it appears. An empty string throws before the request is sent.
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index f3316f3e..ab9fea74 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -139,7 +139,7 @@ export default class McpClient { * Mints a session token for one user against a connection's MCP server. * * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all + * `/mcp/v3/connections/`, which exposes all * of that connection's tools without an MCP configuration. Use this method * to mint a token for that server; use {@link McpClient.createSessionToken} * for a Virtual MCP server built from a configuration. A token works only on @@ -153,10 +153,10 @@ export default class McpClient { * than the run is expected to take. * * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. Pass it exactly as stored, - * including case. The connection is found regardless of case, but the token - * is bound to the stored name, so a server URL built with different casing - * refuses it. + * `connectionName` elsewhere in the SDK. It is matched without regard to + * case, and the token is always issued for the server URL built from the + * connection's stored name. That URL is case-sensitive, so connect using the + * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. * @param params.expirySeconds Token lifetime in whole seconds, from 60 @@ -183,6 +183,7 @@ export default class McpClient { * expirySeconds: 900, * accessLevel: 'READ_ONLY', * }); + * // The URL path is case-sensitive: use the connection's stored name. * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; * // Hand serverUrl and session.token to your MCP client * ``` diff --git a/lib/mcp.js b/lib/mcp.js index 6a6e0039..2a07a189 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -166,7 +166,7 @@ class McpClient { * Mints a session token for one user against a connection's MCP server. * * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all + * `/mcp/v3/connections/`, which exposes all * of that connection's tools without an MCP configuration. Use this method * to mint a token for that server; use {@link McpClient.createSessionToken} * for a Virtual MCP server built from a configuration. A token works only on @@ -180,10 +180,10 @@ class McpClient { * than the run is expected to take. * * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. Pass it exactly as stored, - * including case. The connection is found regardless of case, but the token - * is bound to the stored name, so a server URL built with different casing - * refuses it. + * `connectionName` elsewhere in the SDK. It is matched without regard to + * case, and the token is always issued for the server URL built from the + * connection's stored name. That URL is case-sensitive, so connect using the + * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. * @param params.expirySeconds Token lifetime in whole seconds, from 60 @@ -210,6 +210,7 @@ class McpClient { * expirySeconds: 900, * accessLevel: 'READ_ONLY', * }); + * // The URL path is case-sensitive: use the connection's stored name. * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; * // Hand serverUrl and session.token to your MCP client * ``` diff --git a/lib/mcp.js.map b/lib/mcp.js.map index faddb701..87fd6227 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,iDAAmE;AACnE,gDAAwD;AAIxD,8DAiB2C;AAQ3C;;;;;;;;;;;;GAYG;AACH,MAAqB,SAAS;IAG5B,YACmB,WAAwB,EACxB,UAAsB;QADtB,gBAAW,GAAX,WAAW,CAAa;QACxB,eAAU,GAAV,UAAU,CAAY;QAEvC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,mBAAU,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACG,YAAY,CAAC,MAMlB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,EAAE;gBACnC,MAAM,EAAE,IAAA,iBAAM,EAAC,wBAAe,gCAC5B,IAAI,EAAE,MAAM,CAAC,IAAI,IACd,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;oBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;iBAChC,CAAC,GACC,CAAC,MAAM,CAAC,sBAAsB,IAAI;oBACnC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;iBACtD,CAAC,EACF;aACH,CAAC,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;OAKG;IACG,SAAS,CAAC,QAAgB;;YAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,YAAY,EACxB,IAAA,iBAAM,EAAC,kCAAyB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAChD,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;OAQG;IACG,WAAW,CAAC,OAIjB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,cAAc,EAC1B,IAAA,iBAAM,EAAC,oCAA2B,gDAC7B,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,KAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,GAC/C,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,MAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,GACnE,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS,KAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,EAC3D,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;OAWG;IACG,YAAY,CAAC,MAMlB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,gCACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IACtB,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,GACC,CAAC,MAAM,CAAC,sBAAsB,IAAI;gBACnC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;aACtD,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;OAKG;IACG,YAAY,CAAC,QAAgB;;YACjC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,EAAE,EAAE,QAAQ,EAAE,CAAC,CACnD,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;OAYG;IACG,qBAAqB,CAAC,MAI3B;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,wBAAwB,EACpC,IAAA,iBAAM,EAAC,8CAAqC,kBAC1C,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,IAAI;gBAC1C,eAAe,EAAE,MAAM,CAAC,eAAe;aACxC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,kBAAkB,CAAC,MAKxB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EACjC,IAAA,iBAAM,EAAC,2CAAkC,gCACvC,WAAW,EAAE,MAAM,CAAC,WAAW,EAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,GACjC,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;IACG,4BAA4B,CAAC,MAKlC;;YACC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC3B,qEAAqE;gBACrE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EACjC,IAAA,iBAAM,EAAC,2CAAkC,gCACvC,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,GACjC,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;CACF;AAnRD,4BAmRC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,aAAiC;IAGpD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5D,gEAAgE;QAChE,MAAM,IAAI,KAAK,CACb,iDAAiD,aAAa,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,IAAA,iBAAM,EAAC,oBAAc,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;KACnE,CAAC;AACJ,CAAC"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/mcp.ts b/src/mcp.ts index 50a2c781..e194e1b4 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -246,7 +246,7 @@ export default class McpClient { * Mints a session token for one user against a connection's MCP server. * * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all + * `/mcp/v3/connections/`, which exposes all * of that connection's tools without an MCP configuration. Use this method * to mint a token for that server; use {@link McpClient.createSessionToken} * for a Virtual MCP server built from a configuration. A token works only on @@ -260,10 +260,10 @@ export default class McpClient { * than the run is expected to take. * * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. Pass it exactly as stored, - * including case. The connection is found regardless of case, but the token - * is bound to the stored name, so a server URL built with different casing - * refuses it. + * `connectionName` elsewhere in the SDK. It is matched without regard to + * case, and the token is always issued for the server URL built from the + * connection's stored name. That URL is case-sensitive, so connect using the + * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user * whose connected account the token acts as. * @param params.expirySeconds Token lifetime in whole seconds, from 60 @@ -290,6 +290,7 @@ export default class McpClient { * expirySeconds: 900, * accessLevel: 'READ_ONLY', * }); + * // The URL path is case-sensitive: use the connection's stored name. * const serverUrl = `${process.env.SCALEKIT_ENVIRONMENT_URL}/mcp/v3/connections/gmail`; * // Hand serverUrl and session.token to your MCP client * ``` From 788b4b1cd24d18c26fd47215e6706f53e891977f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 15:48:29 +0000 Subject: [PATCH 6/9] docs(mcp): document identifier length for connection session tokens State that identifier is 1 to 255 characters, and tighten the release note for createConnectionSessionToken. Co-Authored-By: Claude Opus 5.5 --- .../added-20261009-153301-mcp-connection-session-token.yaml | 3 ++- REFERENCE.md | 2 +- lib/mcp.d.ts | 2 +- lib/mcp.js | 2 +- src/mcp.ts | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml index 2fa2edbb..9ff61111 100644 --- a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -1,6 +1,6 @@ kind: added body: | - `scalekit.actions.mcp.createConnectionSessionToken(params)` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`, built from the connection's stored name), which exposes all of that connection's tools without an MCP configuration. It takes the same `identifier`, `expirySeconds` and `accessLevel` as `createSessionToken`. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + `scalekit.actions.mcp.createConnectionSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration; `accessLevel: 'READ_ONLY'` limits the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) ```ts const session = await scalekit.actions.mcp.createConnectionSessionToken({ @@ -9,6 +9,7 @@ body: | expirySeconds: 900, accessLevel: 'READ_ONLY', }); + const headers = { Authorization: `Bearer ${session.token}` }; ``` time: '2026-10-09T15:33:01.189094+00:00' custom: diff --git a/REFERENCE.md b/REFERENCE.md index ca88cb4c..869f117b 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -8058,7 +8058,7 @@ console.log(serverUrl, session.token, session.expiresAt);
-**params.identifier:** `string` - Your application's identifier for the end user whose connected account the token acts as +**params.identifier:** `string` - Your application's identifier for the end user whose connected account the token acts as, 1 to 255 characters
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index ab9fea74..5b07971b 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -158,7 +158,7 @@ export default class McpClient { * connection's stored name. That URL is case-sensitive, so connect using the * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as. + * whose connected account the token acts as, 1 to 255 characters. * @param params.expirySeconds Token lifetime in whole seconds, from 60 * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it diff --git a/lib/mcp.js b/lib/mcp.js index 2a07a189..3a5d1619 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -185,7 +185,7 @@ class McpClient { * connection's stored name. That URL is case-sensitive, so connect using the * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as. + * whose connected account the token acts as, 1 to 255 characters. * @param params.expirySeconds Token lifetime in whole seconds, from 60 * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it diff --git a/src/mcp.ts b/src/mcp.ts index e194e1b4..17cb8edb 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -265,7 +265,7 @@ export default class McpClient { * connection's stored name. That URL is case-sensitive, so connect using the * stored name exactly as it appears. * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as. + * whose connected account the token acts as, 1 to 255 characters. * @param params.expirySeconds Token lifetime in whole seconds, from 60 * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it From fa752170607030ee19020c578eed9e87fd156733 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 20:21:54 +0000 Subject: [PATCH 7/9] feat(mcp): mint connection session tokens through createSessionToken createSessionToken now accepts either mcpConfigId or connectionName. The params type is a union so TypeScript rejects both and neither, and the method also checks at runtime that exactly one non-empty target is set before sending a request. A connectionName is sent as keyId; calls with mcpConfigId are unchanged. The separate connection method is removed, and the reference and release note describe the new form. Co-Authored-By: Claude Opus 5.5 --- ...9-153301-mcp-connection-session-token.yaml | 4 +- REFERENCE.md | 111 +++------- lib/index.d.ts | 2 +- lib/index.js.map | 2 +- lib/mcp.d.ts | 130 +++++++----- lib/mcp.js | 132 ++++++------ lib/mcp.js.map | 2 +- src/index.ts | 5 +- src/mcp.ts | 182 +++++++++------- tests/actions.test.ts | 22 +- tests/mcp-session-token.test.ts | 196 ++++++++++++++---- 11 files changed, 459 insertions(+), 329 deletions(-) diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml index 9ff61111..b3655ae3 100644 --- a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -1,9 +1,9 @@ kind: added body: | - `scalekit.actions.mcp.createConnectionSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration; `accessLevel: 'READ_ONLY'` limits the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + `scalekit.actions.mcp.createSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration. Pass `connectionName` instead of `mcpConfigId` (exactly one of the two; existing `mcpConfigId` calls are unchanged), and `accessLevel: 'READ_ONLY'` limits the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) ```ts - const session = await scalekit.actions.mcp.createConnectionSessionToken({ + const session = await scalekit.actions.mcp.createSessionToken({ connectionName: 'gmail', identifier: 'user_123', expirySeconds: 900, diff --git a/REFERENCE.md b/REFERENCE.md index 869f117b..3858e1e5 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -7493,7 +7493,7 @@ console.log(connectedAccount?.status === ConnectorStatus.ACTIVE); ## Virtual MCP Servers -A Virtual MCP server exposes a chosen set of connections and tools over the Model Context Protocol, so any MCP-capable agent can call them. Create one configuration per agent role (not per user), and mint a short-lived session token per user per run. The server URL stays the same; the token carries the user's identity. Each AgentKit connection also has its own MCP server with all of its tools; mint tokens for it with `createConnectionSessionToken`. +A Virtual MCP server exposes a chosen set of connections and tools over the Model Context Protocol, so any MCP-capable agent can call them. Create one configuration per agent role (not per user), and mint a short-lived session token per user per run. The server URL stays the same; the token carries the user's identity. Each AgentKit connection also has its own MCP server with all of its tools; mint tokens for it with `createSessionToken({ connectionName, ... })`. Only the generally available `McpConfig` API is covered. The older `Mcp` and `McpInstance` families are marked PREVIEW and are not exposed. @@ -7921,9 +7921,16 @@ for (const account of pending) {
-Mints a session token for one user against one configuration. +Mints a session token for one user, for either a Virtual MCP server or a connection's own MCP server. Pass exactly one target: -The token carries the user's identity. Mint a fresh one before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. +- `mcpConfigId`: the token works on that configuration's `config.mcpServerUrl`. +- `connectionName`: the token works on the connection's own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. The name is matched without regard to case, but the URL path is case-sensitive: build it from the connection's name exactly as stored. + +A token works only on the server it was minted for. The token carries the user's identity. Mint a fresh one before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. + +Setting both targets, neither, or an empty one throws before the request is sent; TypeScript rejects both and neither at compile time. + +For a connection, the user should have an active connected account on it. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection or the identifier or expiry is rejected.
@@ -7938,6 +7945,7 @@ The token carries the user's identity. Mint a fresh one before every agent run,
```typescript +// Virtual MCP server built from a configuration const { config } = await scalekitClient.actions.mcp.getConfig(''); const session = await scalekitClient.actions.mcp.createSessionToken({ mcpConfigId: '', @@ -7948,86 +7956,10 @@ const session = await scalekitClient.actions.mcp.createSessionToken({ // Hand both to your MCP client console.log(config?.mcpServerUrl, session.token, session.expiresAt); ``` -
- - - - -#### ⚙️ Parameters - -
-
- -
-
- -**params.mcpConfigId:** `string` - The configuration - -
-
-
-
- -**params.identifier:** `string` - Your application's identifier for the end user - -
-
-
-
- -**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server enforces the allowed range. - -
-
-
-
- -**params.accessLevel?:** `'FULL' | 'READ_ONLY'` - Tools the token can use. `'READ_ONLY'` limits the token to tools annotated read-only (`read_only_hint`): other tools are left out of the tool list and refused when called. `'FULL'`, or omitting it, exposes every tool the configuration exposes. - -
-
-
-
- - - - - - -
client.actions.mcp.createConnectionSessionToken(params) -> Promise<CreateMcpSessionTokenResponse> -
-
- -#### 📝 Description - -
-
- -
-
- -Mints a session token for one user against a connection's MCP server. - -Every AgentKit connection has its own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. Use this method for that server and `createSessionToken` for a Virtual MCP server built from a configuration; a token works only on the server it was minted for. - -The user should have an active connected account on the connection. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. Mint a fresh token before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. - -The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection or the identifier or expiry is rejected. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
```typescript -const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ +// A connection's own MCP server +const session = await scalekitClient.actions.mcp.createSessionToken({ connectionName: 'gmail', identifier: 'user_123', expirySeconds: 900, @@ -8045,34 +7977,43 @@ console.log(serverUrl, session.token, session.expiresAt); #### ⚙️ Parameters +Type: `CreateMcpSessionTokenParams`. Set exactly one of `mcpConfigId` and `connectionName`. + +
+
+
+**params.mcpConfigId?:** `string` - The configuration. Set this or `connectionName`. + +
+
-**params.connectionName:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. It is matched without regard to case, and the token is always issued for the server URL built from the connection's stored name. That URL is case-sensitive, so connect using the stored name exactly as it appears. An empty string throws before the request is sent. +**params.connectionName?:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. Set this or `mcpConfigId`. It is matched without regard to case, and the token is always issued for the server URL built from the connection's stored name. That URL is case-sensitive, so connect using the stored name exactly as it appears.
-**params.identifier:** `string` - Your application's identifier for the end user whose connected account the token acts as, 1 to 255 characters +**params.identifier:** `string` - Your application's identifier for the end user, 1 to 255 characters. For a connection, the user whose connected account the token acts as.
-**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server accepts 60 seconds to 24 hours (86400) and defaults to 1 hour (3600) when omitted. +**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server enforces the allowed range; for a connection it accepts 60 seconds to 24 hours (86400) and defaults to 1 hour (3600) when omitted.
-**params.accessLevel?:** `'FULL' | 'READ_ONLY'` - Tools the token can use. `'READ_ONLY'` limits the token to tools annotated read-only (`read_only_hint`): other tools are left out of the tool list and refused when called. `'FULL'`, or omitting it, exposes every tool the connection provides. +**params.accessLevel?:** `'FULL' | 'READ_ONLY'` - Tools the token can use. `'READ_ONLY'` limits the token to tools annotated read-only (`read_only_hint`): other tools are left out of the tool list and refused when called. `'FULL'`, or omitting it, exposes every tool the configuration or connection exposes.
diff --git a/lib/index.d.ts b/lib/index.d.ts index e48fe06a..7ca96de4 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -16,7 +16,7 @@ export { ToolReadinessState } from './pkg/grpc/scalekit/v1/tools/tools_pb'; export type { SearchToolsResponse, SearchedTool, ConnectionReadiness, } from './pkg/grpc/scalekit/v1/tools/tools_pb'; export type { ListToolsResponse, Tool, } from './pkg/grpc/scalekit/v1/tools/tools_pb'; export type { ActionTool, ListToolsResult } from './actions'; -export type { McpSessionTokenAccessLevel } from './mcp'; +export type { McpSessionTokenAccessLevel, CreateMcpSessionTokenParams, } from './mcp'; export type { McpConfig, McpConfigConnectionToolMapping, CreateMcpConfigResponse, GetMcpConfigResponse, ListMcpConfigsResponse, UpdateMcpConfigResponse, DeleteMcpConfigResponse, ListMcpConnectedAccountsResponse, CreateMcpSessionTokenResponse, } from './pkg/grpc/scalekit/v1/mcp/mcp_pb'; export type { AuthPattern, AuthField, Provider, CreateProviderResponse, UpdateProviderResponse, ListProvidersResponse, } from './providers'; export { ProviderType } from './pkg/grpc/scalekit/v1/providers/providers_pb'; diff --git a/lib/index.js.map b/lib/index.js.map index a45ff27e..fcb2b8d2 100644 --- a/lib/index.js.map +++ b/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,0DAAwC;AAE/B,yBAFF,kBAAc,CAEE;AACI,mBAHpB,kBAAc,CAGc;AACnC,kBAAe,kBAAc,CAAC;AAE9B,mDAAiC;AACjC,+CAA6B;AAE7B,2CAAyB;AAEzB,uEAAuE;AACvE,2EAA2E;AAC3E,yEAAyE;AACzE,wEAAyE;AAAhE,0GAAA,YAAY,OAAA;AAQrB,iFAAiF;AACjF,mFAAmF;AACnF,8EAA8E;AAC9E,qEAA6E;AAApE,uGAAA,UAAU,OAAA;AAAE,mGAAA,MAAM,OAAA;AAG3B,2EAA2E;AAC3E,mFAAmF;AACnF,oFAAoF;AACpF,mFAAmF;AACnF,kFAAkF;AAClF,oFAAoF;AACpF,yGAGyE;AAFvE,wHAAA,eAAe,OAAA;AACf,sHAAA,aAAa,OAAA;AAGf,8EAA8E;AAC9E,iFAAiF;AACjF,0EAA0E;AAC1E,0EAA0E;AAC1E,oFAI2D;AAHzD,gHAAA,cAAc,OAAA;AACd,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAYpB,uEAAuE;AACvE,iFAAiF;AACjF,2FAA2F;AAC3F,+EAA+E;AAC/E,kEAA2E;AAAlE,8GAAA,kBAAkB,OAAA;AAkD3B,8EAA6E;AAApE,4GAAA,YAAY,OAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,0DAAwC;AAE/B,yBAFF,kBAAc,CAEE;AACI,mBAHpB,kBAAc,CAGc;AACnC,kBAAe,kBAAc,CAAC;AAE9B,mDAAiC;AACjC,+CAA6B;AAE7B,2CAAyB;AAEzB,uEAAuE;AACvE,2EAA2E;AAC3E,yEAAyE;AACzE,wEAAyE;AAAhE,0GAAA,YAAY,OAAA;AAQrB,iFAAiF;AACjF,mFAAmF;AACnF,8EAA8E;AAC9E,qEAA6E;AAApE,uGAAA,UAAU,OAAA;AAAE,mGAAA,MAAM,OAAA;AAG3B,2EAA2E;AAC3E,mFAAmF;AACnF,oFAAoF;AACpF,mFAAmF;AACnF,kFAAkF;AAClF,oFAAoF;AACpF,yGAGyE;AAFvE,wHAAA,eAAe,OAAA;AACf,sHAAA,aAAa,OAAA;AAGf,8EAA8E;AAC9E,iFAAiF;AACjF,0EAA0E;AAC1E,0EAA0E;AAC1E,oFAI2D;AAHzD,gHAAA,cAAc,OAAA;AACd,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAYpB,uEAAuE;AACvE,iFAAiF;AACjF,2FAA2F;AAC3F,+EAA+E;AAC/E,kEAA2E;AAAlE,8GAAA,kBAAkB,OAAA;AAqD3B,8EAA6E;AAApE,4GAAA,YAAY,OAAA"} \ No newline at end of file diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index 5b07971b..d30a4587 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -7,6 +7,27 @@ import { CreateMcpConfigResponse, CreateMcpSessionTokenResponse, DeleteMcpConfig * for tools annotated read-only only. */ export type McpSessionTokenAccessLevel = 'FULL' | 'READ_ONLY'; +/** + * Parameters for {@link McpClient.createSessionToken}. Set exactly one target: + * `mcpConfigId` for a Virtual MCP server built from a configuration, or + * `connectionName` for a connection's own MCP server. + */ +export type CreateMcpSessionTokenParams = { + /** Your application's unique identifier for the user, 1 to 255 characters. */ + identifier: string; + /** Token lifetime in whole seconds. Omit to use the server default. */ + expirySeconds?: number; + /** Tools the token can use. Omit for `'FULL'`. */ + accessLevel?: McpSessionTokenAccessLevel; +} & ({ + /** ID of the MCP configuration whose server the token is for. */ + mcpConfigId: string; + connectionName?: never; +} | { + /** Name of the AgentKit connection whose MCP server the token is for. */ + connectionName: string; + mcpConfigId?: never; +}); /** * Client for Virtual MCP servers. * @@ -113,71 +134,71 @@ export default class McpClient { includeAuthLink?: boolean; }): Promise; /** - * Mints a session token for one user against one configuration. - * - * The server URL is static; this token is what carries user identity. Mint a - * fresh one before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. + * Mints a session token for one user, for either a Virtual MCP server or a + * connection's own MCP server. * - * @param params.mcpConfigId ID of the configuration. - * @param params.identifier Your application's unique identifier for the user. - * @param params.expirySeconds Token lifetime in whole seconds. - * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it - * to tools annotated read-only: other tools are left out of the tool list - * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * configuration exposes. - * @throws {Error} If `expirySeconds` is not a positive integer. - * @throws {ScalekitServerException} If a network or server error occurs. - */ - createSessionToken(params: { - mcpConfigId: string; - identifier: string; - expirySeconds?: number; - accessLevel?: McpSessionTokenAccessLevel; - }): Promise; - /** - * Mints a session token for one user against a connection's MCP server. + * Pass exactly one target: * - * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all - * of that connection's tools without an MCP configuration. Use this method - * to mint a token for that server; use {@link McpClient.createSessionToken} - * for a Virtual MCP server built from a configuration. A token works only on - * the server it was minted for. + * - `mcpConfigId`: the token works on that configuration's + * `config.mcpServerUrl` (see {@link McpClient.getConfig}). + * - `connectionName`: the token works on the connection's own MCP server at + * `/mcp/v3/connections/`, which exposes + * all of that connection's tools without an MCP configuration. The name is + * matched without regard to case, but the URL path is case-sensitive: + * build it from the connection's name exactly as stored. * - * The user should have an active connected account on the connection. When - * they do not, the server either rejects the call, or creates a pending - * connected account and returns a token whose tool calls report the account - * as not connected, depending on the environment. Mint a fresh token before + * A token works only on the server it was minted for. The server URL is + * static; the token is what carries user identity. Mint a fresh one before * every agent run and never reuse one across runs. Set the expiry longer * than the run is expected to take. * - * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. It is matched without regard to - * case, and the token is always issued for the server URL built from the - * connection's stored name. That URL is case-sensitive, so connect using the - * stored name exactly as it appears. - * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as, 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds, from 60 - * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. + * For a connection, the user should have an active connected account on it. + * When they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. + * + * @param params.mcpConfigId ID of the configuration. Set this or + * `connectionName`, not both. + * @param params.connectionName Name of an AgentKit connection: the same + * value used as `connectionName` elsewhere in the SDK. Set this or + * `mcpConfigId`, not both. + * @param params.identifier Your application's unique identifier for the user, + * 1 to 255 characters. + * @param params.expirySeconds Token lifetime in whole seconds. For a + * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 + * (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * connection provides. + * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a - * positive integer. - * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the identifier or expiry is rejected, or (in environments that - * do not create pending accounts) the user has no active connected account - * on it. - * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is + * set, the one set is empty, or `expirySeconds` is not a positive integer. + * No request is sent. + * @throws {ScalekitNotFoundException} If `connectionName` matches no active + * connection. + * @throws {ScalekitBadRequestException} If the request is otherwise rejected: + * for example the connection is not an AgentKit connection, the identifier + * or expiry is out of range, or (in environments that do not create pending + * accounts) the user has no active connected account on the connection. * @throws {ScalekitServerException} If a network or server error occurs. * * @example * ```typescript - * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * // Virtual MCP server built from a configuration + * const { config } = await scalekitClient.actions.mcp.getConfig(configId); + * const session = await scalekitClient.actions.mcp.createSessionToken({ + * mcpConfigId: configId, + * identifier: 'user_123', + * expirySeconds: 900, + * }); + * // Hand config?.mcpServerUrl and session.token to your MCP client + * ``` + * + * @example + * ```typescript + * // A connection's own MCP server + * const session = await scalekitClient.actions.mcp.createSessionToken({ * connectionName: 'gmail', * identifier: 'user_123', * expirySeconds: 900, @@ -188,10 +209,5 @@ export default class McpClient { * // Hand serverUrl and session.token to your MCP client * ``` */ - createConnectionSessionToken(params: { - connectionName: string; - identifier: string; - expirySeconds?: number; - accessLevel?: McpSessionTokenAccessLevel; - }): Promise; + createSessionToken(params: CreateMcpSessionTokenParams): Promise; } diff --git a/lib/mcp.js b/lib/mcp.js index 3a5d1619..8bbe35b7 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -139,72 +139,71 @@ class McpClient { }); } /** - * Mints a session token for one user against one configuration. + * Mints a session token for one user, for either a Virtual MCP server or a + * connection's own MCP server. * - * The server URL is static; this token is what carries user identity. Mint a - * fresh one before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. + * Pass exactly one target: * - * @param params.mcpConfigId ID of the configuration. - * @param params.identifier Your application's unique identifier for the user. - * @param params.expirySeconds Token lifetime in whole seconds. - * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it - * to tools annotated read-only: other tools are left out of the tool list - * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * configuration exposes. - * @throws {Error} If `expirySeconds` is not a positive integer. - * @throws {ScalekitServerException} If a network or server error occurs. - */ - createSessionToken(params) { - return __awaiter(this, void 0, void 0, function* () { - return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign({ mcpConfigId: params.mcpConfigId, identifier: params.identifier }, expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { - accessLevel: params.accessLevel, - })))); - }); - } - /** - * Mints a session token for one user against a connection's MCP server. - * - * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all - * of that connection's tools without an MCP configuration. Use this method - * to mint a token for that server; use {@link McpClient.createSessionToken} - * for a Virtual MCP server built from a configuration. A token works only on - * the server it was minted for. + * - `mcpConfigId`: the token works on that configuration's + * `config.mcpServerUrl` (see {@link McpClient.getConfig}). + * - `connectionName`: the token works on the connection's own MCP server at + * `/mcp/v3/connections/`, which exposes + * all of that connection's tools without an MCP configuration. The name is + * matched without regard to case, but the URL path is case-sensitive: + * build it from the connection's name exactly as stored. * - * The user should have an active connected account on the connection. When - * they do not, the server either rejects the call, or creates a pending - * connected account and returns a token whose tool calls report the account - * as not connected, depending on the environment. Mint a fresh token before + * A token works only on the server it was minted for. The server URL is + * static; the token is what carries user identity. Mint a fresh one before * every agent run and never reuse one across runs. Set the expiry longer * than the run is expected to take. * - * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. It is matched without regard to - * case, and the token is always issued for the server URL built from the - * connection's stored name. That URL is case-sensitive, so connect using the - * stored name exactly as it appears. - * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as, 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds, from 60 - * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. + * For a connection, the user should have an active connected account on it. + * When they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. + * + * @param params.mcpConfigId ID of the configuration. Set this or + * `connectionName`, not both. + * @param params.connectionName Name of an AgentKit connection: the same + * value used as `connectionName` elsewhere in the SDK. Set this or + * `mcpConfigId`, not both. + * @param params.identifier Your application's unique identifier for the user, + * 1 to 255 characters. + * @param params.expirySeconds Token lifetime in whole seconds. For a + * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 + * (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * connection provides. + * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a - * positive integer. - * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the identifier or expiry is rejected, or (in environments that - * do not create pending accounts) the user has no active connected account - * on it. - * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is + * set, the one set is empty, or `expirySeconds` is not a positive integer. + * No request is sent. + * @throws {ScalekitNotFoundException} If `connectionName` matches no active + * connection. + * @throws {ScalekitBadRequestException} If the request is otherwise rejected: + * for example the connection is not an AgentKit connection, the identifier + * or expiry is out of range, or (in environments that do not create pending + * accounts) the user has no active connected account on the connection. * @throws {ScalekitServerException} If a network or server error occurs. * * @example * ```typescript - * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * // Virtual MCP server built from a configuration + * const { config } = await scalekitClient.actions.mcp.getConfig(configId); + * const session = await scalekitClient.actions.mcp.createSessionToken({ + * mcpConfigId: configId, + * identifier: 'user_123', + * expirySeconds: 900, + * }); + * // Hand config?.mcpServerUrl and session.token to your MCP client + * ``` + * + * @example + * ```typescript + * // A connection's own MCP server + * const session = await scalekitClient.actions.mcp.createSessionToken({ * connectionName: 'gmail', * identifier: 'user_123', * expirySeconds: 900, @@ -215,19 +214,36 @@ class McpClient { * // Hand serverUrl and session.token to your MCP client * ``` */ - createConnectionSessionToken(params) { + createSessionToken(params) { return __awaiter(this, void 0, void 0, function* () { - if (!params.connectionName) { - // An empty name would otherwise reach the server as "no target set". - throw new Error('connectionName is required'); - } - return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign({ keyId: params.connectionName, identifier: params.identifier }, expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { + return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign(Object.assign(Object.assign({}, sessionTokenTarget(params)), { identifier: params.identifier }), expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { accessLevel: params.accessLevel, })))); }); } } exports.default = McpClient; +/** + * Validates that exactly one session-token target is set and returns the + * matching request field: `mcpConfigId` for a configuration, `keyId` for a + * connection. The server requires exactly one of the two, so the other is + * never set. + */ +function sessionTokenTarget(params) { + // Checked at runtime too: plain JavaScript callers bypass the union type. + const hasConfig = params.mcpConfigId != null; + const hasConnection = params.connectionName != null; + if (hasConfig === hasConnection) { + throw new Error('Set exactly one of mcpConfigId or connectionName, ' + + (hasConfig ? 'not both' : 'got neither')); + } + const name = hasConfig ? 'mcpConfigId' : 'connectionName'; + const value = hasConfig ? params.mcpConfigId : params.connectionName; + if (typeof value !== 'string' || value === '') { + throw new Error(`${name} must be a non-empty string`); + } + return hasConfig ? { mcpConfigId: value } : { keyId: value }; +} /** * Validates `expirySeconds` and returns the request's `expiry` field, or no * field when it is omitted so the server applies its default. diff --git a/lib/mcp.js.map b/lib/mcp.js.map index 87fd6227..e03fb215 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,iDAAmE;AACnE,gDAAwD;AAIxD,8DAiB2C;AAQ3C;;;;;;;;;;;;GAYG;AACH,MAAqB,SAAS;IAG5B,YACmB,WAAwB,EACxB,UAAsB;QADtB,gBAAW,GAAX,WAAW,CAAa;QACxB,eAAU,GAAV,UAAU,CAAY;QAEvC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,mBAAU,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACG,YAAY,CAAC,MAMlB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,EAAE;gBACnC,MAAM,EAAE,IAAA,iBAAM,EAAC,wBAAe,gCAC5B,IAAI,EAAE,MAAM,CAAC,IAAI,IACd,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;oBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;iBAChC,CAAC,GACC,CAAC,MAAM,CAAC,sBAAsB,IAAI;oBACnC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;iBACtD,CAAC,EACF;aACH,CAAC,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;OAKG;IACG,SAAS,CAAC,QAAgB;;YAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,YAAY,EACxB,IAAA,iBAAM,EAAC,kCAAyB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAChD,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;OAQG;IACG,WAAW,CAAC,OAIjB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,cAAc,EAC1B,IAAA,iBAAM,EAAC,oCAA2B,gDAC7B,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,KAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,GAC/C,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,MAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,GACnE,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS,KAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,EAC3D,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;OAWG;IACG,YAAY,CAAC,MAMlB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,gCACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IACtB,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,GACC,CAAC,MAAM,CAAC,sBAAsB,IAAI;gBACnC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;aACtD,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;OAKG;IACG,YAAY,CAAC,QAAgB;;YACjC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,eAAe,EAC3B,IAAA,iBAAM,EAAC,qCAA4B,EAAE,EAAE,QAAQ,EAAE,CAAC,CACnD,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;OAYG;IACG,qBAAqB,CAAC,MAI3B;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,wBAAwB,EACpC,IAAA,iBAAM,EAAC,8CAAqC,kBAC1C,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,IAAI;gBAC1C,eAAe,EAAE,MAAM,CAAC,eAAe;aACxC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,kBAAkB,CAAC,MAKxB;;YACC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EACjC,IAAA,iBAAM,EAAC,2CAAkC,gCACvC,WAAW,EAAE,MAAM,CAAC,WAAW,EAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,GACjC,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoDG;IACG,4BAA4B,CAAC,MAKlC;;YACC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC3B,qEAAqE;gBACrE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAChC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EACjC,IAAA,iBAAM,EAAC,2CAAkC,gCACvC,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,UAAU,EAAE,MAAM,CAAC,UAAU,IAC1B,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,GACjC,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI;gBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,EACF,CACH,CAAC;QACJ,CAAC;KAAA;CACF;AApRD,4BAoRC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,aAAiC;IAGpD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5D,gEAAgE;QAChE,MAAM,IAAI,KAAK,CACb,iDAAiD,aAAa,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,IAAA,iBAAM,EAAC,oBAAc,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;KACnE,CAAC;AACJ,CAAC"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fb83c32a..5c36fbd7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -78,7 +78,10 @@ export type { // Normalized types surfaced in the public `actions.listTools` API. export type { ActionTool, ListToolsResult } from './actions'; -export type { McpSessionTokenAccessLevel } from './mcp'; +export type { + McpSessionTokenAccessLevel, + CreateMcpSessionTokenParams, +} from './mcp'; // Types surfaced by the public `mcp` client (Virtual MCP servers). Only the // generally available McpConfig surface is re-exported; the PREVIEW `Mcp` and diff --git a/src/mcp.ts b/src/mcp.ts index 17cb8edb..aaf9e30a 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -28,6 +28,31 @@ import { */ export type McpSessionTokenAccessLevel = 'FULL' | 'READ_ONLY'; +/** + * Parameters for {@link McpClient.createSessionToken}. Set exactly one target: + * `mcpConfigId` for a Virtual MCP server built from a configuration, or + * `connectionName` for a connection's own MCP server. + */ +export type CreateMcpSessionTokenParams = { + /** Your application's unique identifier for the user, 1 to 255 characters. */ + identifier: string; + /** Token lifetime in whole seconds. Omit to use the server default. */ + expirySeconds?: number; + /** Tools the token can use. Omit for `'FULL'`. */ + accessLevel?: McpSessionTokenAccessLevel; +} & ( + | { + /** ID of the MCP configuration whose server the token is for. */ + mcpConfigId: string; + connectionName?: never; + } + | { + /** Name of the AgentKit connection whose MCP server the token is for. */ + connectionName: string; + mcpConfigId?: never; + } +); + /** * Client for Virtual MCP servers. * @@ -207,84 +232,71 @@ export default class McpClient { } /** - * Mints a session token for one user against one configuration. - * - * The server URL is static; this token is what carries user identity. Mint a - * fresh one before every agent run and never reuse one across runs. Set the - * expiry longer than the run is expected to take. + * Mints a session token for one user, for either a Virtual MCP server or a + * connection's own MCP server. * - * @param params.mcpConfigId ID of the configuration. - * @param params.identifier Your application's unique identifier for the user. - * @param params.expirySeconds Token lifetime in whole seconds. - * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it - * to tools annotated read-only: other tools are left out of the tool list - * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * configuration exposes. - * @throws {Error} If `expirySeconds` is not a positive integer. - * @throws {ScalekitServerException} If a network or server error occurs. - */ - async createSessionToken(params: { - mcpConfigId: string; - identifier: string; - expirySeconds?: number; - accessLevel?: McpSessionTokenAccessLevel; - }): Promise { - return this.coreClient.connectExec( - this.client.createMcpSessionToken, - create(CreateMcpSessionTokenRequestSchema, { - mcpConfigId: params.mcpConfigId, - identifier: params.identifier, - ...expiryField(params.expirySeconds), - ...(params.accessLevel !== undefined && { - accessLevel: params.accessLevel, - }), - }) - ); - } - - /** - * Mints a session token for one user against a connection's MCP server. + * Pass exactly one target: * - * Every AgentKit connection has its own MCP server at - * `/mcp/v3/connections/`, which exposes all - * of that connection's tools without an MCP configuration. Use this method - * to mint a token for that server; use {@link McpClient.createSessionToken} - * for a Virtual MCP server built from a configuration. A token works only on - * the server it was minted for. + * - `mcpConfigId`: the token works on that configuration's + * `config.mcpServerUrl` (see {@link McpClient.getConfig}). + * - `connectionName`: the token works on the connection's own MCP server at + * `/mcp/v3/connections/`, which exposes + * all of that connection's tools without an MCP configuration. The name is + * matched without regard to case, but the URL path is case-sensitive: + * build it from the connection's name exactly as stored. * - * The user should have an active connected account on the connection. When - * they do not, the server either rejects the call, or creates a pending - * connected account and returns a token whose tool calls report the account - * as not connected, depending on the environment. Mint a fresh token before + * A token works only on the server it was minted for. The server URL is + * static; the token is what carries user identity. Mint a fresh one before * every agent run and never reuse one across runs. Set the expiry longer * than the run is expected to take. * - * @param params.connectionName The connection name: the same value used as - * `connectionName` elsewhere in the SDK. It is matched without regard to - * case, and the token is always issued for the server URL built from the - * connection's stored name. That URL is case-sensitive, so connect using the - * stored name exactly as it appears. - * @param params.identifier Your application's unique identifier for the user - * whose connected account the token acts as, 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds, from 60 - * (1 minute) to 86400 (24 hours). Defaults to 3600 (1 hour) when omitted. + * For a connection, the user should have an active connected account on it. + * When they do not, the server either rejects the call, or creates a pending + * connected account and returns a token whose tool calls report the account + * as not connected, depending on the environment. + * + * @param params.mcpConfigId ID of the configuration. Set this or + * `connectionName`, not both. + * @param params.connectionName Name of an AgentKit connection: the same + * value used as `connectionName` elsewhere in the SDK. Set this or + * `mcpConfigId`, not both. + * @param params.identifier Your application's unique identifier for the user, + * 1 to 255 characters. + * @param params.expirySeconds Token lifetime in whole seconds. For a + * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 + * (1 hour) when omitted. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the - * connection provides. + * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `connectionName` is empty, or `expirySeconds` is not a - * positive integer. - * @throws {ScalekitBadRequestException} If the connection is not an AgentKit - * connection, the identifier or expiry is rejected, or (in environments that - * do not create pending accounts) the user has no active connected account - * on it. - * @throws {ScalekitNotFoundException} If no active connection has that name. + * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is + * set, the one set is empty, or `expirySeconds` is not a positive integer. + * No request is sent. + * @throws {ScalekitNotFoundException} If `connectionName` matches no active + * connection. + * @throws {ScalekitBadRequestException} If the request is otherwise rejected: + * for example the connection is not an AgentKit connection, the identifier + * or expiry is out of range, or (in environments that do not create pending + * accounts) the user has no active connected account on the connection. * @throws {ScalekitServerException} If a network or server error occurs. * * @example * ```typescript - * const session = await scalekitClient.actions.mcp.createConnectionSessionToken({ + * // Virtual MCP server built from a configuration + * const { config } = await scalekitClient.actions.mcp.getConfig(configId); + * const session = await scalekitClient.actions.mcp.createSessionToken({ + * mcpConfigId: configId, + * identifier: 'user_123', + * expirySeconds: 900, + * }); + * // Hand config?.mcpServerUrl and session.token to your MCP client + * ``` + * + * @example + * ```typescript + * // A connection's own MCP server + * const session = await scalekitClient.actions.mcp.createSessionToken({ * connectionName: 'gmail', * identifier: 'user_123', * expirySeconds: 900, @@ -295,20 +307,13 @@ export default class McpClient { * // Hand serverUrl and session.token to your MCP client * ``` */ - async createConnectionSessionToken(params: { - connectionName: string; - identifier: string; - expirySeconds?: number; - accessLevel?: McpSessionTokenAccessLevel; - }): Promise { - if (!params.connectionName) { - // An empty name would otherwise reach the server as "no target set". - throw new Error('connectionName is required'); - } + async createSessionToken( + params: CreateMcpSessionTokenParams + ): Promise { return this.coreClient.connectExec( this.client.createMcpSessionToken, create(CreateMcpSessionTokenRequestSchema, { - keyId: params.connectionName, + ...sessionTokenTarget(params), identifier: params.identifier, ...expiryField(params.expirySeconds), ...(params.accessLevel !== undefined && { @@ -319,6 +324,33 @@ export default class McpClient { } } +/** + * Validates that exactly one session-token target is set and returns the + * matching request field: `mcpConfigId` for a configuration, `keyId` for a + * connection. The server requires exactly one of the two, so the other is + * never set. + */ +function sessionTokenTarget(params: { + mcpConfigId?: unknown; + connectionName?: unknown; +}): { mcpConfigId: string } | { keyId: string } { + // Checked at runtime too: plain JavaScript callers bypass the union type. + const hasConfig = params.mcpConfigId != null; + const hasConnection = params.connectionName != null; + if (hasConfig === hasConnection) { + throw new Error( + 'Set exactly one of mcpConfigId or connectionName, ' + + (hasConfig ? 'not both' : 'got neither') + ); + } + const name = hasConfig ? 'mcpConfigId' : 'connectionName'; + const value = hasConfig ? params.mcpConfigId : params.connectionName; + if (typeof value !== 'string' || value === '') { + throw new Error(`${name} must be a non-empty string`); + } + return hasConfig ? { mcpConfigId: value } : { keyId: value }; +} + /** * Validates `expirySeconds` and returns the request's `expiry` field, or no * field when it is omitted so the server applies its default. diff --git a/tests/actions.test.ts b/tests/actions.test.ts index 0ad1185b..9b05bee1 100644 --- a/tests/actions.test.ts +++ b/tests/actions.test.ts @@ -188,15 +188,9 @@ describe('Actions', () => { }); }); - describe('mcp.createConnectionSessionToken', () => { - it('should expose createConnectionSessionToken', () => { - expect(typeof client.actions.mcp.createConnectionSessionToken).toBe( - 'function' - ); - }); - + describe('mcp.createSessionToken with connectionName', () => { it('should mint a read-only token for the connection MCP server', async () => { - const response = await client.actions.mcp.createConnectionSessionToken({ + const response = await client.actions.mcp.createSessionToken({ connectionName: GMAIL_CONNECTION_NAME, identifier: GMAIL_IDENTIFIER, expirySeconds: 300, @@ -210,12 +204,22 @@ describe('Actions', () => { it('should reject an unknown connection with ScalekitNotFoundException', async () => { await expect( - client.actions.mcp.createConnectionSessionToken({ + client.actions.mcp.createSessionToken({ connectionName: `missing-connection-${Date.now()}`, identifier: GMAIL_IDENTIFIER, }) ).rejects.toBeInstanceOf(ScalekitNotFoundException); }); + + it('should reject both targets before any request', async () => { + await expect( + client.actions.mcp.createSessionToken({ + mcpConfigId: 'cfg_unused', + connectionName: GMAIL_CONNECTION_NAME, + identifier: GMAIL_IDENTIFIER, + } as any) + ).rejects.toThrow('Set exactly one of mcpConfigId or connectionName'); + }); }); describe('getAuthorizationLink', () => { diff --git a/tests/mcp-session-token.test.ts b/tests/mcp-session-token.test.ts index 7fc71c0e..b3034b3c 100644 --- a/tests/mcp-session-token.test.ts +++ b/tests/mcp-session-token.test.ts @@ -1,15 +1,18 @@ /** - * McpClient.createSessionToken must send `accessLevel` only when the caller - * sets it. An omitted access level leaves the field empty on the wire, which - * the server treats as full access, so existing callers keep their tools. + * McpClient.createSessionToken targets either an MCP configuration + * (`mcpConfigId`) or a connection's own MCP server (`connectionName`, sent as + * `keyId`). The server requires exactly one of the two, so the SDK rejects + * both, neither, or an empty target before sending anything, and never sets + * the other field. * - * McpClient.createConnectionSessionToken must target the connection's MCP - * server through `keyId` and never set `mcpConfigId`: the server requires - * exactly one of the two. + * `accessLevel` is sent only when the caller sets it. An omitted access level + * leaves the field empty on the wire, which the server treats as full access, + * so existing callers keep their tools. */ import { describe, it, expect, jest } from '@jest/globals'; import CoreClient from '../src/core'; import McpClient from '../src/mcp'; +import type { CreateMcpSessionTokenParams } from '../src'; function makeMcpClient() { const coreClient = new CoreClient( @@ -26,26 +29,52 @@ function makeMcpClient() { return { mcp: new McpClient(grpcConnect, coreClient), createMcpSessionToken }; } -describe('McpClient.createSessionToken accessLevel', () => { - it('leaves accessLevel empty when omitted', async () => { +describe('McpClient.createSessionToken with mcpConfigId', () => { + it('sends mcpConfigId and never sets keyId', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); - await mcp.createSessionToken({ mcpConfigId: 'cfg_1', identifier: 'u1' }); + const res = await mcp.createSessionToken({ + mcpConfigId: 'cfg_1', + identifier: 'u1', + }); + expect(res.token).toBe('tok'); + expect(createMcpSessionToken).toHaveBeenCalledTimes(1); const req = createMcpSessionToken.mock.calls[0][0]; - expect(req.accessLevel).toBe(''); expect(req.mcpConfigId).toBe('cfg_1'); + expect(req.keyId).toBe(''); expect(req.identifier).toBe('u1'); + expect(req.expiry).toBeUndefined(); + expect(req.accessLevel).toBe(''); }); - it.each(['READ_ONLY', 'FULL'] as const)('sends %s', async (level) => { + it('accepts a params object typed the way existing callers wrote it', async () => { + // Existing callers may hold the params in a variable of this shape; it + // must stay assignable to the new union type. + const legacy: { + mcpConfigId: string; + identifier: string; + expirySeconds?: number; + accessLevel?: 'FULL' | 'READ_ONLY'; + } = { mcpConfigId: 'cfg_1', identifier: 'u1', expirySeconds: 60 }; const { mcp, createMcpSessionToken } = makeMcpClient(); - await mcp.createSessionToken({ - mcpConfigId: 'cfg_1', - identifier: 'u1', - accessLevel: level, - }); - expect(createMcpSessionToken.mock.calls[0][0].accessLevel).toBe(level); + await mcp.createSessionToken(legacy); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.mcpConfigId).toBe('cfg_1'); + expect(req.expiry?.seconds).toBe(BigInt(60)); }); + it.each(['READ_ONLY', 'FULL'] as const)( + 'sends accessLevel %s', + async (level) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ + mcpConfigId: 'cfg_1', + identifier: 'u1', + accessLevel: level, + }); + expect(createMcpSessionToken.mock.calls[0][0].accessLevel).toBe(level); + } + ); + it('sends accessLevel alongside expiry', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); await mcp.createSessionToken({ @@ -60,10 +89,10 @@ describe('McpClient.createSessionToken accessLevel', () => { }); }); -describe('McpClient.createConnectionSessionToken', () => { - it('targets the connection by keyId and leaves mcpConfigId empty', async () => { +describe('McpClient.createSessionToken with connectionName', () => { + it('targets the connection by keyId and never sets mcpConfigId', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); - const res = await mcp.createConnectionSessionToken({ + const res = await mcp.createSessionToken({ connectionName: 'gmail', identifier: 'u1', }); @@ -77,9 +106,18 @@ describe('McpClient.createConnectionSessionToken', () => { expect(req.accessLevel).toBe(''); }); + it('sends the name unchanged (the server matches it)', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ + connectionName: 'Gmail-Work', + identifier: 'u1', + }); + expect(createMcpSessionToken.mock.calls[0][0].keyId).toBe('Gmail-Work'); + }); + it('forwards expiry and accessLevel', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); - await mcp.createConnectionSessionToken({ + await mcp.createSessionToken({ connectionName: 'gmail', identifier: 'u1', expirySeconds: 900, @@ -94,29 +132,87 @@ describe('McpClient.createConnectionSessionToken', () => { it('sends FULL when set explicitly', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); - await mcp.createConnectionSessionToken({ + await mcp.createSessionToken({ connectionName: 'gmail', identifier: 'u1', accessLevel: 'FULL', }); expect(createMcpSessionToken.mock.calls[0][0].accessLevel).toBe('FULL'); }); +}); + +describe('McpClient.createSessionToken target validation', () => { + // Plain JavaScript callers bypass the union type, so these go through `any`. + const invalid: Array<[string, Record, string]> = [ + [ + 'both targets', + { mcpConfigId: 'cfg_1', connectionName: 'gmail' }, + 'Set exactly one of mcpConfigId or connectionName, not both', + ], + [ + 'both targets, one empty', + { mcpConfigId: 'cfg_1', connectionName: '' }, + 'Set exactly one of mcpConfigId or connectionName, not both', + ], + [ + 'neither target', + {}, + 'Set exactly one of mcpConfigId or connectionName, got neither', + ], + [ + 'both targets undefined', + { mcpConfigId: undefined, connectionName: undefined }, + 'Set exactly one of mcpConfigId or connectionName, got neither', + ], + [ + 'empty mcpConfigId', + { mcpConfigId: '' }, + 'mcpConfigId must be a non-empty string', + ], + [ + 'empty connectionName', + { connectionName: '' }, + 'connectionName must be a non-empty string', + ], + [ + 'non-string connectionName', + { connectionName: 42 }, + 'connectionName must be a non-empty string', + ], + ]; + + it.each(invalid)( + 'rejects %s before any request', + async (_label, target, message) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await expect( + mcp.createSessionToken({ ...target, identifier: 'u1' } as any) + ).rejects.toThrow(message); + expect(createMcpSessionToken).not.toHaveBeenCalled(); + } + ); - it('rejects an empty connectionName before any request', async () => { + it('treats a null target as absent', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); - await expect( - mcp.createConnectionSessionToken({ connectionName: '', identifier: 'u1' }) - ).rejects.toThrow('connectionName is required'); - expect(createMcpSessionToken).not.toHaveBeenCalled(); + await mcp.createSessionToken({ + mcpConfigId: null, + connectionName: 'gmail', + identifier: 'u1', + } as any); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.keyId).toBe('gmail'); + expect(req.mcpConfigId).toBe(''); }); +}); +describe('McpClient.createSessionToken expiry validation', () => { it.each([0, -1, 900.5, NaN, Infinity])( - 'rejects expirySeconds=%p before any request', + 'rejects expirySeconds=%p for a configuration before any request', async (expirySeconds) => { const { mcp, createMcpSessionToken } = makeMcpClient(); await expect( - mcp.createConnectionSessionToken({ - connectionName: 'gmail', + mcp.createSessionToken({ + mcpConfigId: 'cfg_1', identifier: 'u1', expirySeconds, }) @@ -124,16 +220,14 @@ describe('McpClient.createConnectionSessionToken', () => { expect(createMcpSessionToken).not.toHaveBeenCalled(); } ); -}); -describe('McpClient.createSessionToken expiry validation', () => { - it.each([0, -1, 900.5, NaN])( - 'still rejects expirySeconds=%p before any request', + it.each([0, -1, 900.5, NaN, Infinity])( + 'rejects expirySeconds=%p for a connection before any request', async (expirySeconds) => { const { mcp, createMcpSessionToken } = makeMcpClient(); await expect( mcp.createSessionToken({ - mcpConfigId: 'cfg_1', + connectionName: 'gmail', identifier: 'u1', expirySeconds, }) @@ -141,10 +235,34 @@ describe('McpClient.createSessionToken expiry validation', () => { expect(createMcpSessionToken).not.toHaveBeenCalled(); } ); +}); - it('never sets keyId', async () => { - const { mcp, createMcpSessionToken } = makeMcpClient(); - await mcp.createSessionToken({ mcpConfigId: 'cfg_1', identifier: 'u1' }); - expect(createMcpSessionToken.mock.calls[0][0].keyId).toBe(''); +describe('CreateMcpSessionTokenParams type', () => { + // Compile-time checks: ts-jest type-checks this file, so if the union ever + // accepts an invalid shape below, the unused expect-error directive fails + // the suite. + it('accepts exactly one target', () => { + const byConfig: CreateMcpSessionTokenParams = { + mcpConfigId: 'cfg_1', + identifier: 'u1', + }; + const byConnection: CreateMcpSessionTokenParams = { + connectionName: 'gmail', + identifier: 'u1', + accessLevel: 'READ_ONLY', + }; + // @ts-expect-error both targets set + const both: CreateMcpSessionTokenParams = { + mcpConfigId: 'cfg_1', + connectionName: 'gmail', + identifier: 'u1', + }; + // @ts-expect-error neither target set + const neither: CreateMcpSessionTokenParams = { identifier: 'u1' }; + // @ts-expect-error identifier is required + const noIdentifier: CreateMcpSessionTokenParams = { connectionName: 'g' }; + expect([byConfig, byConnection, both, neither, noIdentifier]).toHaveLength( + 5 + ); }); }); From 87935761d4e0298784ae333448e40ea1974b33de Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 20:30:54 +0000 Subject: [PATCH 8/9] fix(mcp): keep mcpConfigId session-token calls unchanged createSessionToken validates expirySeconds first, as before, and only checks the target for the connectionName form. Without a non-empty connectionName the request is built exactly as in v2.19.0: mcpConfigId is passed through as given, an empty connectionName is ignored, and a missing ID still reaches the server. A connectionName alongside mcpConfigId, or a non-string connectionName, throws before any request. Tests pin the unchanged mcpConfigId behaviour, and the live connection test deletes a connected account only if it created one. The reference and release note say the new checks apply to connectionName only. Co-Authored-By: Claude Opus 5.5 --- ...9-153301-mcp-connection-session-token.yaml | 2 +- REFERENCE.md | 4 +- lib/mcp.d.ts | 11 +- lib/mcp.js | 48 ++++---- lib/mcp.js.map | 2 +- src/mcp.ts | 62 ++++++----- tests/actions.test.ts | 48 ++++++-- tests/mcp-session-token.test.ts | 105 +++++++++++++----- 8 files changed, 193 insertions(+), 89 deletions(-) diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml index b3655ae3..af660037 100644 --- a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -1,6 +1,6 @@ kind: added body: | - `scalekit.actions.mcp.createSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration. Pass `connectionName` instead of `mcpConfigId` (exactly one of the two; existing `mcpConfigId` calls are unchanged), and `accessLevel: 'READ_ONLY'` limits the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + `scalekit.actions.mcp.createSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration. Pass `connectionName` instead of `mcpConfigId`; setting both throws before the request is sent. Existing `mcpConfigId` calls are unchanged and get no new checks. Add `accessLevel: 'READ_ONLY'` to limit the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) ```ts const session = await scalekit.actions.mcp.createSessionToken({ diff --git a/REFERENCE.md b/REFERENCE.md index 3858e1e5..83dffab6 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -7928,7 +7928,7 @@ Mints a session token for one user, for either a Virtual MCP server or a connect A token works only on the server it was minted for. The token carries the user's identity. Mint a fresh one before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. -Setting both targets, neither, or an empty one throws before the request is sent; TypeScript rejects both and neither at compile time. +TypeScript rejects both targets and neither at compile time. At run time, only the `connectionName` form is checked before the request is sent: a non-string `connectionName`, or `mcpConfigId` set as well, throws. Calls with `mcpConfigId` are unchanged: they are not checked beyond `expirySeconds`, so a missing or empty ID is sent and rejected by the server, and an empty `connectionName` alongside it is ignored. For a connection, the user should have an active connected account on it. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection or the identifier or expiry is rejected.
@@ -7985,7 +7985,7 @@ Type: `CreateMcpSessionTokenParams`. Set exactly one of `mcpConfigId` and `conne
-**params.mcpConfigId?:** `string` - The configuration. Set this or `connectionName`. +**params.mcpConfigId?:** `string` - The configuration. Set this or `connectionName`. Used when `connectionName` is omitted or empty.
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index d30a4587..8743c2fb 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -158,7 +158,8 @@ export default class McpClient { * as not connected, depending on the environment. * * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. + * `connectionName`, not both. Used when `connectionName` is omitted or + * empty. * @param params.connectionName Name of an AgentKit connection: the same * value used as `connectionName` elsewhere in the SDK. Set this or * `mcpConfigId`, not both. @@ -172,9 +173,11 @@ export default class McpClient { * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is - * set, the one set is empty, or `expirySeconds` is not a positive integer. - * No request is sent. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for the + * `connectionName` form only, if `connectionName` is not a string or + * `mcpConfigId` is set as well. No request is sent. Calls with + * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they + * always have: a missing or empty ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: diff --git a/lib/mcp.js b/lib/mcp.js index 8bbe35b7..88120b0a 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -163,7 +163,8 @@ class McpClient { * as not connected, depending on the environment. * * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. + * `connectionName`, not both. Used when `connectionName` is omitted or + * empty. * @param params.connectionName Name of an AgentKit connection: the same * value used as `connectionName` elsewhere in the SDK. Set this or * `mcpConfigId`, not both. @@ -177,9 +178,11 @@ class McpClient { * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is - * set, the one set is empty, or `expirySeconds` is not a positive integer. - * No request is sent. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for the + * `connectionName` form only, if `connectionName` is not a string or + * `mcpConfigId` is set as well. No request is sent. Calls with + * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they + * always have: a missing or empty ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: @@ -216,7 +219,10 @@ class McpClient { */ createSessionToken(params) { return __awaiter(this, void 0, void 0, function* () { - return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign(Object.assign(Object.assign({}, sessionTokenTarget(params)), { identifier: params.identifier }), expiryField(params.expirySeconds)), (params.accessLevel !== undefined && { + // Expiry first, so mcpConfigId calls fail exactly as they always have. + const expiry = expiryField(params.expirySeconds); + const target = sessionTokenTarget(params); + return this.coreClient.connectExec(this.client.createMcpSessionToken, (0, protobuf_1.create)(mcp_pb_1.CreateMcpSessionTokenRequestSchema, Object.assign(Object.assign(Object.assign(Object.assign({}, target), { identifier: params.identifier }), expiry), (params.accessLevel !== undefined && { accessLevel: params.accessLevel, })))); }); @@ -224,25 +230,29 @@ class McpClient { } exports.default = McpClient; /** - * Validates that exactly one session-token target is set and returns the - * matching request field: `mcpConfigId` for a configuration, `keyId` for a - * connection. The server requires exactly one of the two, so the other is + * Returns the request's target field: `keyId` for a connection, otherwise + * `mcpConfigId`. The server requires exactly one of the two, so the other is * never set. + * + * Only the `connectionName` form is checked here. Without a non-empty + * `connectionName` the call is a configuration call and `mcpConfigId` is + * passed through as given, unchecked, so existing callers keep their + * behaviour: a missing or empty ID still reaches the server, and a stray + * empty `connectionName` is ignored. */ function sessionTokenTarget(params) { - // Checked at runtime too: plain JavaScript callers bypass the union type. - const hasConfig = params.mcpConfigId != null; - const hasConnection = params.connectionName != null; - if (hasConfig === hasConnection) { - throw new Error('Set exactly one of mcpConfigId or connectionName, ' + - (hasConfig ? 'not both' : 'got neither')); + // Read as unknown: plain JavaScript callers bypass the union type. + const { mcpConfigId, connectionName } = params; + if (connectionName == null || connectionName === '') { + return { mcpConfigId: params.mcpConfigId }; + } + if (typeof connectionName !== 'string') { + throw new Error('connectionName must be a non-empty string'); } - const name = hasConfig ? 'mcpConfigId' : 'connectionName'; - const value = hasConfig ? params.mcpConfigId : params.connectionName; - if (typeof value !== 'string' || value === '') { - throw new Error(`${name} must be a non-empty string`); + if (mcpConfigId != null) { + throw new Error('Set exactly one of mcpConfigId or connectionName, not both'); } - return hasConfig ? { mcpConfigId: value } : { keyId: value }; + return { keyId: connectionName }; } /** * Validates `expirySeconds` and returns the request's `expiry` field, or no diff --git a/lib/mcp.js.map b/lib/mcp.js.map index e03fb215..f548bc37 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/mcp.ts b/src/mcp.ts index aaf9e30a..1e191a2d 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -256,7 +256,8 @@ export default class McpClient { * as not connected, depending on the environment. * * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. + * `connectionName`, not both. Used when `connectionName` is omitted or + * empty. * @param params.connectionName Name of an AgentKit connection: the same * value used as `connectionName` elsewhere in the SDK. Set this or * `mcpConfigId`, not both. @@ -270,9 +271,11 @@ export default class McpClient { * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If not exactly one of `mcpConfigId` and `connectionName` is - * set, the one set is empty, or `expirySeconds` is not a positive integer. - * No request is sent. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for the + * `connectionName` form only, if `connectionName` is not a string or + * `mcpConfigId` is set as well. No request is sent. Calls with + * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they + * always have: a missing or empty ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: @@ -310,12 +313,15 @@ export default class McpClient { async createSessionToken( params: CreateMcpSessionTokenParams ): Promise { + // Expiry first, so mcpConfigId calls fail exactly as they always have. + const expiry = expiryField(params.expirySeconds); + const target = sessionTokenTarget(params); return this.coreClient.connectExec( this.client.createMcpSessionToken, create(CreateMcpSessionTokenRequestSchema, { - ...sessionTokenTarget(params), + ...target, identifier: params.identifier, - ...expiryField(params.expirySeconds), + ...expiry, ...(params.accessLevel !== undefined && { accessLevel: params.accessLevel, }), @@ -325,30 +331,36 @@ export default class McpClient { } /** - * Validates that exactly one session-token target is set and returns the - * matching request field: `mcpConfigId` for a configuration, `keyId` for a - * connection. The server requires exactly one of the two, so the other is + * Returns the request's target field: `keyId` for a connection, otherwise + * `mcpConfigId`. The server requires exactly one of the two, so the other is * never set. + * + * Only the `connectionName` form is checked here. Without a non-empty + * `connectionName` the call is a configuration call and `mcpConfigId` is + * passed through as given, unchecked, so existing callers keep their + * behaviour: a missing or empty ID still reaches the server, and a stray + * empty `connectionName` is ignored. */ -function sessionTokenTarget(params: { - mcpConfigId?: unknown; - connectionName?: unknown; -}): { mcpConfigId: string } | { keyId: string } { - // Checked at runtime too: plain JavaScript callers bypass the union type. - const hasConfig = params.mcpConfigId != null; - const hasConnection = params.connectionName != null; - if (hasConfig === hasConnection) { +function sessionTokenTarget( + params: CreateMcpSessionTokenParams +): { mcpConfigId?: string } | { keyId: string } { + // Read as unknown: plain JavaScript callers bypass the union type. + const { mcpConfigId, connectionName } = params as { + mcpConfigId?: unknown; + connectionName?: unknown; + }; + if (connectionName == null || connectionName === '') { + return { mcpConfigId: params.mcpConfigId }; + } + if (typeof connectionName !== 'string') { + throw new Error('connectionName must be a non-empty string'); + } + if (mcpConfigId != null) { throw new Error( - 'Set exactly one of mcpConfigId or connectionName, ' + - (hasConfig ? 'not both' : 'got neither') + 'Set exactly one of mcpConfigId or connectionName, not both' ); } - const name = hasConfig ? 'mcpConfigId' : 'connectionName'; - const value = hasConfig ? params.mcpConfigId : params.connectionName; - if (typeof value !== 'string' || value === '') { - throw new Error(`${name} must be a non-empty string`); - } - return hasConfig ? { mcpConfigId: value } : { keyId: value }; + return { keyId: connectionName }; } /** diff --git a/tests/actions.test.ts b/tests/actions.test.ts index 9b05bee1..125c9ee6 100644 --- a/tests/actions.test.ts +++ b/tests/actions.test.ts @@ -190,16 +190,46 @@ describe('Actions', () => { describe('mcp.createSessionToken with connectionName', () => { it('should mint a read-only token for the connection MCP server', async () => { - const response = await client.actions.mcp.createSessionToken({ - connectionName: GMAIL_CONNECTION_NAME, - identifier: GMAIL_IDENTIFIER, - expirySeconds: 300, - accessLevel: 'READ_ONLY', - }); + // The call uses the shared ACTIVE fixture account, which must survive. + // Where the fixture is missing, the server may create a pending + // connected account instead; delete only an account this test created. + const accountExisted = await client.actions + .getConnectedAccount({ + connectionName: GMAIL_CONNECTION_NAME, + identifier: GMAIL_IDENTIFIER, + }) + .then( + () => true, + (error: unknown) => { + if (error instanceof ScalekitNotFoundException) return false; + throw error; + } + ); + + try { + const response = await client.actions.mcp.createSessionToken({ + connectionName: GMAIL_CONNECTION_NAME, + identifier: GMAIL_IDENTIFIER, + expirySeconds: 300, + accessLevel: 'READ_ONLY', + }); - expect(typeof response.token).toBe('string'); - expect(response.token.length).toBeGreaterThan(0); - expect(response.expiresAt).toBeDefined(); + expect(typeof response.token).toBe('string'); + expect(response.token.length).toBeGreaterThan(0); + expect(response.expiresAt).toBeDefined(); + } finally { + if (!accountExisted) { + await client.actions + .deleteConnectedAccount({ + connectionName: GMAIL_CONNECTION_NAME, + identifier: GMAIL_IDENTIFIER, + }) + .catch((error: unknown) => { + // Nothing was created (the call was rejected): nothing to clean. + if (!(error instanceof ScalekitNotFoundException)) throw error; + }); + } + } }); it('should reject an unknown connection with ScalekitNotFoundException', async () => { diff --git a/tests/mcp-session-token.test.ts b/tests/mcp-session-token.test.ts index b3034b3c..1192efff 100644 --- a/tests/mcp-session-token.test.ts +++ b/tests/mcp-session-token.test.ts @@ -1,18 +1,22 @@ /** * McpClient.createSessionToken targets either an MCP configuration * (`mcpConfigId`) or a connection's own MCP server (`connectionName`, sent as - * `keyId`). The server requires exactly one of the two, so the SDK rejects - * both, neither, or an empty target before sending anything, and never sets - * the other field. + * `keyId`). The server requires exactly one of the two; the SDK never sets + * the other field. Only the connectionName form is checked before sending: + * mcpConfigId calls build the same request they always did, unchecked. * * `accessLevel` is sent only when the caller sets it. An omitted access level * leaves the field empty on the wire, which the server treats as full access, * so existing callers keep their tools. */ import { describe, it, expect, jest } from '@jest/globals'; +import { create } from '@bufbuild/protobuf'; +import { Code, ConnectError } from '@connectrpc/connect'; import CoreClient from '../src/core'; import McpClient from '../src/mcp'; import type { CreateMcpSessionTokenParams } from '../src'; +import { ScalekitBadRequestException, ScalekitException } from '../src/errors'; +import { CreateMcpSessionTokenRequestSchema } from '../src/pkg/grpc/scalekit/v1/mcp/mcp_pb'; function makeMcpClient() { const coreClient = new CoreClient( @@ -141,7 +145,7 @@ describe('McpClient.createSessionToken with connectionName', () => { }); }); -describe('McpClient.createSessionToken target validation', () => { +describe('McpClient.createSessionToken connectionName validation', () => { // Plain JavaScript callers bypass the union type, so these go through `any`. const invalid: Array<[string, Record, string]> = [ [ @@ -150,32 +154,12 @@ describe('McpClient.createSessionToken target validation', () => { 'Set exactly one of mcpConfigId or connectionName, not both', ], [ - 'both targets, one empty', - { mcpConfigId: 'cfg_1', connectionName: '' }, + 'an empty mcpConfigId alongside connectionName', + { mcpConfigId: '', connectionName: 'gmail' }, 'Set exactly one of mcpConfigId or connectionName, not both', ], [ - 'neither target', - {}, - 'Set exactly one of mcpConfigId or connectionName, got neither', - ], - [ - 'both targets undefined', - { mcpConfigId: undefined, connectionName: undefined }, - 'Set exactly one of mcpConfigId or connectionName, got neither', - ], - [ - 'empty mcpConfigId', - { mcpConfigId: '' }, - 'mcpConfigId must be a non-empty string', - ], - [ - 'empty connectionName', - { connectionName: '' }, - 'connectionName must be a non-empty string', - ], - [ - 'non-string connectionName', + 'a non-string connectionName', { connectionName: 42 }, 'connectionName must be a non-empty string', ], @@ -192,7 +176,7 @@ describe('McpClient.createSessionToken target validation', () => { } ); - it('treats a null target as absent', async () => { + it('treats a null mcpConfigId as absent', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); await mcp.createSessionToken({ mcpConfigId: null, @@ -205,6 +189,71 @@ describe('McpClient.createSessionToken target validation', () => { }); }); +describe('McpClient.createSessionToken mcpConfigId calls are unchecked', () => { + // Without a non-empty connectionName the SDK adds no checks beyond + // expirySeconds: the request is built exactly as it always was, from the + // values given, and the server decides. These pin that behaviour. + function configRequest(mcpConfigId: unknown) { + return create(CreateMcpSessionTokenRequestSchema, { + mcpConfigId: mcpConfigId as string, + identifier: 'u1', + }); + } + + it.each([ + ['no target', {}, undefined], + [ + 'both targets undefined', + { mcpConfigId: undefined, connectionName: undefined }, + undefined, + ], + ['an empty mcpConfigId', { mcpConfigId: '' }, ''], + ['a null mcpConfigId', { mcpConfigId: null }, null], + [ + 'a stray empty connectionName', + { mcpConfigId: 'cfg_1', connectionName: '' }, + 'cfg_1', + ], + [ + 'a null connectionName', + { mcpConfigId: 'cfg_1', connectionName: null }, + 'cfg_1', + ], + ['only an empty connectionName', { connectionName: '' }, undefined], + ])('sends the request for %s', async (_label, target, mcpConfigId) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ ...target, identifier: 'u1' } as any); + expect(createMcpSessionToken).toHaveBeenCalledTimes(1); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req).toEqual(configRequest(mcpConfigId)); + expect(req.keyId).toBe(''); + }); + + it('surfaces the server rejection as a ScalekitException subclass', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + createMcpSessionToken.mockImplementation(async () => { + throw new ConnectError('mcp_config_id is required', Code.InvalidArgument); + }); + const call = mcp.createSessionToken({ identifier: 'u1' } as any); + await expect(call).rejects.toBeInstanceOf(ScalekitBadRequestException); + await expect(call).rejects.toBeInstanceOf(ScalekitException); + expect(createMcpSessionToken).toHaveBeenCalledTimes(1); + }); + + it('checks expirySeconds before the target', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await expect( + mcp.createSessionToken({ + mcpConfigId: 'cfg_1', + connectionName: 'gmail', + identifier: 'u1', + expirySeconds: 0, + } as any) + ).rejects.toThrow('expirySeconds must be a positive integer, got 0'); + expect(createMcpSessionToken).not.toHaveBeenCalled(); + }); +}); + describe('McpClient.createSessionToken expiry validation', () => { it.each([0, -1, 900.5, NaN, Infinity])( 'rejects expirySeconds=%p for a configuration before any request', From 249e1085ef1052f44a28dc7cf9b8cd59becdc513 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 20:41:09 +0000 Subject: [PATCH 9/9] fix(mcp): use mcpConfigId when both session-token targets are set A call that sets both mcpConfigId and connectionName, for example by spreading a context object that carries connectionName, compiled and minted a configuration token before. It does again: a non-empty mcpConfigId always selects the configuration and the request is built as before. connectionName is used only when mcpConfigId is missing or empty, and only that form is checked before the request, now including an empty identifier ("identifier is required"). The params type lets the configuration form carry connectionName and still rejects a call with neither target or without an identifier. The reference and release note describe the precedence and the token lifetime range for both targets. Co-Authored-By: Claude Opus 5.5 --- ...9-153301-mcp-connection-session-token.yaml | 2 +- REFERENCE.md | 14 +- lib/mcp.d.ts | 41 ++--- lib/mcp.js | 56 +++---- lib/mcp.js.map | 2 +- src/mcp.ts | 72 +++++---- tests/actions.test.ts | 10 +- tests/mcp-session-token.test.ts | 145 ++++++++++++++---- 8 files changed, 221 insertions(+), 121 deletions(-) diff --git a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml index af660037..6ed919a4 100644 --- a/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml +++ b/.changes/unreleased/added-20261009-153301-mcp-connection-session-token.yaml @@ -1,6 +1,6 @@ kind: added body: | - `scalekit.actions.mcp.createSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration. Pass `connectionName` instead of `mcpConfigId`; setting both throws before the request is sent. Existing `mcpConfigId` calls are unchanged and get no new checks. Add `accessLevel: 'READ_ONLY'` to limit the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) + `scalekit.actions.mcp.createSessionToken({ connectionName, identifier, expirySeconds?, accessLevel? })` mints a session token for a connection's own MCP server (`/mcp/v3/connections/`), which serves all of that connection's tools without an MCP configuration. Pass `connectionName` instead of `mcpConfigId`; if both are set, `mcpConfigId` is used. Existing `mcpConfigId` calls are unchanged. For either target, `accessLevel: 'READ_ONLY'` limits the token to read-only tools. The URL path is case-sensitive, so connect with the connection's name exactly as stored. [Reference](https://github.com/scalekit-inc/scalekit-sdk-node/blob/main/REFERENCE.md#virtual-mcp-servers) ```ts const session = await scalekit.actions.mcp.createSessionToken({ diff --git a/REFERENCE.md b/REFERENCE.md index 83dffab6..88de61a6 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -7921,14 +7921,14 @@ for (const account of pending) {
-Mints a session token for one user, for either a Virtual MCP server or a connection's own MCP server. Pass exactly one target: +Mints a session token for one user, for either a Virtual MCP server or a connection's own MCP server. Set a target; if both are set, `mcpConfigId` is used: - `mcpConfigId`: the token works on that configuration's `config.mcpServerUrl`. - `connectionName`: the token works on the connection's own MCP server at `/mcp/v3/connections/`, which exposes all of that connection's tools without an MCP configuration. The name is matched without regard to case, but the URL path is case-sensitive: build it from the connection's name exactly as stored. A token works only on the server it was minted for. The token carries the user's identity. Mint a fresh one before every agent run, never reuse one across runs, and set the expiry longer than the run is expected to take. -TypeScript rejects both targets and neither at compile time. At run time, only the `connectionName` form is checked before the request is sent: a non-string `connectionName`, or `mcpConfigId` set as well, throws. Calls with `mcpConfigId` are unchanged: they are not checked beyond `expirySeconds`, so a missing or empty ID is sent and rejected by the server, and an empty `connectionName` alongside it is ignored. +TypeScript rejects a call with neither target at compile time. At run time, only the connection form is checked before the request is sent: a non-string `connectionName` throws, and so does an `identifier` that is not a non-empty string (`identifier is required`). Calls with `mcpConfigId` are unchanged: they are not checked beyond `expirySeconds`, so a missing or empty ID is sent and rejected by the server, and `connectionName` is ignored when a non-empty `mcpConfigId` is set. For a connection, the user should have an active connected account on it. When they do not, depending on the environment, the call either fails with `ScalekitBadRequestException`, or succeeds after creating a pending connected account, in which case tool calls report the account as not connected. The call fails with `ScalekitNotFoundException` when no active connection has that name, and with `ScalekitBadRequestException` when the connection is not an AgentKit connection or the identifier or expiry is rejected.
@@ -7977,7 +7977,7 @@ console.log(serverUrl, session.token, session.expiresAt); #### ⚙️ Parameters -Type: `CreateMcpSessionTokenParams`. Set exactly one of `mcpConfigId` and `connectionName`. +Type: `CreateMcpSessionTokenParams`. Set `mcpConfigId` or `connectionName`; if both are set, `mcpConfigId` is used.
@@ -7985,28 +7985,28 @@ Type: `CreateMcpSessionTokenParams`. Set exactly one of `mcpConfigId` and `conne
-**params.mcpConfigId?:** `string` - The configuration. Set this or `connectionName`. Used when `connectionName` is omitted or empty. +**params.mcpConfigId?:** `string` - The configuration. If both targets are set, this one is used and `connectionName` is ignored.
-**params.connectionName?:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. Set this or `mcpConfigId`. It is matched without regard to case, and the token is always issued for the server URL built from the connection's stored name. That URL is case-sensitive, so connect using the stored name exactly as it appears. +**params.connectionName?:** `string` - The connection name: the same value used as `connectionName` elsewhere in the SDK. Used when `mcpConfigId` is omitted or empty. It is matched without regard to case, and the token is always issued for the server URL built from the connection's stored name. That URL is case-sensitive, so connect using the stored name exactly as it appears.
-**params.identifier:** `string` - Your application's identifier for the end user, 1 to 255 characters. For a connection, the user whose connected account the token acts as. +**params.identifier:** `string` - Your application's identifier for the end user, 1 to 255 characters. For a connection, the user whose connected account the token acts as; required (non-empty) for a connection before the request is sent.
-**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server enforces the allowed range; for a connection it accepts 60 seconds to 24 hours (86400) and defaults to 1 hour (3600) when omitted. +**params.expirySeconds?:** `number` - Token lifetime in whole seconds. Must be a positive integer; any other value, such as `900.5`, `0` or `NaN`, throws before the request is sent. The server accepts 60 seconds to 24 hours (86400), 1 hour (3600) by default.
diff --git a/lib/mcp.d.ts b/lib/mcp.d.ts index 8743c2fb..097ffa2a 100644 --- a/lib/mcp.d.ts +++ b/lib/mcp.d.ts @@ -8,25 +8,27 @@ import { CreateMcpConfigResponse, CreateMcpSessionTokenResponse, DeleteMcpConfig */ export type McpSessionTokenAccessLevel = 'FULL' | 'READ_ONLY'; /** - * Parameters for {@link McpClient.createSessionToken}. Set exactly one target: + * Parameters for {@link McpClient.createSessionToken}. Set a target: * `mcpConfigId` for a Virtual MCP server built from a configuration, or - * `connectionName` for a connection's own MCP server. + * `connectionName` for a connection's own MCP server. If both are set, + * `mcpConfigId` is used. */ export type CreateMcpSessionTokenParams = { /** Your application's unique identifier for the user, 1 to 255 characters. */ identifier: string; - /** Token lifetime in whole seconds. Omit to use the server default. */ + /** Token lifetime in whole seconds, 60 to 86400. Defaults to 3600. */ expirySeconds?: number; /** Tools the token can use. Omit for `'FULL'`. */ accessLevel?: McpSessionTokenAccessLevel; } & ({ /** ID of the MCP configuration whose server the token is for. */ mcpConfigId: string; - connectionName?: never; + /** Ignored when `mcpConfigId` is set. */ + connectionName?: string; } | { /** Name of the AgentKit connection whose MCP server the token is for. */ connectionName: string; - mcpConfigId?: never; + mcpConfigId?: undefined; }); /** * Client for Virtual MCP servers. @@ -137,7 +139,7 @@ export default class McpClient { * Mints a session token for one user, for either a Virtual MCP server or a * connection's own MCP server. * - * Pass exactly one target: + * Set a target; if both are set, `mcpConfigId` is used: * * - `mcpConfigId`: the token works on that configuration's * `config.mcpServerUrl` (see {@link McpClient.getConfig}). @@ -157,27 +159,26 @@ export default class McpClient { * connected account and returns a token whose tool calls report the account * as not connected, depending on the environment. * - * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. Used when `connectionName` is omitted or - * empty. + * @param params.mcpConfigId ID of the configuration. If both targets are + * set, this one is used and `connectionName` is ignored. * @param params.connectionName Name of an AgentKit connection: the same - * value used as `connectionName` elsewhere in the SDK. Set this or - * `mcpConfigId`, not both. + * value used as `connectionName` elsewhere in the SDK. Used when + * `mcpConfigId` is omitted or empty. * @param params.identifier Your application's unique identifier for the user, - * 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds. For a - * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 - * (1 hour) when omitted. + * 1 to 255 characters. For a connection it is checked before the request: + * an empty value throws `identifier is required`. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * seconds to 24 hours (86400); 1 hour (3600) by default. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `expirySeconds` is not a positive integer, or, for the - * `connectionName` form only, if `connectionName` is not a string or - * `mcpConfigId` is set as well. No request is sent. Calls with - * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they - * always have: a missing or empty ID is sent and rejected by the server. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for a + * connection only, if `connectionName` is not a string or `identifier` is + * empty. No request is sent. Calls with `mcpConfigId` are not checked + * beyond `expirySeconds` and behave as they always have: a missing or empty + * ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: diff --git a/lib/mcp.js b/lib/mcp.js index 88120b0a..0e9a7421 100644 --- a/lib/mcp.js +++ b/lib/mcp.js @@ -142,7 +142,7 @@ class McpClient { * Mints a session token for one user, for either a Virtual MCP server or a * connection's own MCP server. * - * Pass exactly one target: + * Set a target; if both are set, `mcpConfigId` is used: * * - `mcpConfigId`: the token works on that configuration's * `config.mcpServerUrl` (see {@link McpClient.getConfig}). @@ -162,27 +162,26 @@ class McpClient { * connected account and returns a token whose tool calls report the account * as not connected, depending on the environment. * - * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. Used when `connectionName` is omitted or - * empty. + * @param params.mcpConfigId ID of the configuration. If both targets are + * set, this one is used and `connectionName` is ignored. * @param params.connectionName Name of an AgentKit connection: the same - * value used as `connectionName` elsewhere in the SDK. Set this or - * `mcpConfigId`, not both. + * value used as `connectionName` elsewhere in the SDK. Used when + * `mcpConfigId` is omitted or empty. * @param params.identifier Your application's unique identifier for the user, - * 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds. For a - * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 - * (1 hour) when omitted. + * 1 to 255 characters. For a connection it is checked before the request: + * an empty value throws `identifier is required`. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * seconds to 24 hours (86400); 1 hour (3600) by default. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `expirySeconds` is not a positive integer, or, for the - * `connectionName` form only, if `connectionName` is not a string or - * `mcpConfigId` is set as well. No request is sent. Calls with - * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they - * always have: a missing or empty ID is sent and rejected by the server. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for a + * connection only, if `connectionName` is not a string or `identifier` is + * empty. No request is sent. Calls with `mcpConfigId` are not checked + * beyond `expirySeconds` and behave as they always have: a missing or empty + * ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: @@ -230,27 +229,30 @@ class McpClient { } exports.default = McpClient; /** - * Returns the request's target field: `keyId` for a connection, otherwise - * `mcpConfigId`. The server requires exactly one of the two, so the other is - * never set. + * Returns the request's target field: `mcpConfigId` for a configuration, + * `keyId` for a connection. The server requires exactly one of the two, so + * the other is never set. * - * Only the `connectionName` form is checked here. Without a non-empty - * `connectionName` the call is a configuration call and `mcpConfigId` is - * passed through as given, unchecked, so existing callers keep their - * behaviour: a missing or empty ID still reaches the server, and a stray - * empty `connectionName` is ignored. + * A non-empty `mcpConfigId` wins, and so does the configuration form when no + * non-empty `connectionName` is given: `mcpConfigId` is then passed through + * as given, unchecked, so existing callers keep their behaviour (a missing or + * empty ID still reaches the server, and `connectionName` is ignored). Only + * the connection form is checked here. */ function sessionTokenTarget(params) { // Read as unknown: plain JavaScript callers bypass the union type. - const { mcpConfigId, connectionName } = params; - if (connectionName == null || connectionName === '') { + const { mcpConfigId, connectionName, identifier } = params; + const isConnectionCall = (mcpConfigId == null || mcpConfigId === '') && + connectionName != null && + connectionName !== ''; + if (!isConnectionCall) { return { mcpConfigId: params.mcpConfigId }; } if (typeof connectionName !== 'string') { throw new Error('connectionName must be a non-empty string'); } - if (mcpConfigId != null) { - throw new Error('Set exactly one of mcpConfigId or connectionName, not both'); + if (typeof identifier !== 'string' || identifier.trim() === '') { + throw new Error('identifier is required'); } return { keyId: connectionName }; } diff --git a/lib/mcp.js.map b/lib/mcp.js.map index f548bc37..c9dfea2e 100644 --- a/lib/mcp.js.map +++ b/lib/mcp.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/mcp.ts b/src/mcp.ts index 1e191a2d..b281f948 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -29,14 +29,15 @@ import { export type McpSessionTokenAccessLevel = 'FULL' | 'READ_ONLY'; /** - * Parameters for {@link McpClient.createSessionToken}. Set exactly one target: + * Parameters for {@link McpClient.createSessionToken}. Set a target: * `mcpConfigId` for a Virtual MCP server built from a configuration, or - * `connectionName` for a connection's own MCP server. + * `connectionName` for a connection's own MCP server. If both are set, + * `mcpConfigId` is used. */ export type CreateMcpSessionTokenParams = { /** Your application's unique identifier for the user, 1 to 255 characters. */ identifier: string; - /** Token lifetime in whole seconds. Omit to use the server default. */ + /** Token lifetime in whole seconds, 60 to 86400. Defaults to 3600. */ expirySeconds?: number; /** Tools the token can use. Omit for `'FULL'`. */ accessLevel?: McpSessionTokenAccessLevel; @@ -44,12 +45,13 @@ export type CreateMcpSessionTokenParams = { | { /** ID of the MCP configuration whose server the token is for. */ mcpConfigId: string; - connectionName?: never; + /** Ignored when `mcpConfigId` is set. */ + connectionName?: string; } | { /** Name of the AgentKit connection whose MCP server the token is for. */ connectionName: string; - mcpConfigId?: never; + mcpConfigId?: undefined; } ); @@ -235,7 +237,7 @@ export default class McpClient { * Mints a session token for one user, for either a Virtual MCP server or a * connection's own MCP server. * - * Pass exactly one target: + * Set a target; if both are set, `mcpConfigId` is used: * * - `mcpConfigId`: the token works on that configuration's * `config.mcpServerUrl` (see {@link McpClient.getConfig}). @@ -255,27 +257,26 @@ export default class McpClient { * connected account and returns a token whose tool calls report the account * as not connected, depending on the environment. * - * @param params.mcpConfigId ID of the configuration. Set this or - * `connectionName`, not both. Used when `connectionName` is omitted or - * empty. + * @param params.mcpConfigId ID of the configuration. If both targets are + * set, this one is used and `connectionName` is ignored. * @param params.connectionName Name of an AgentKit connection: the same - * value used as `connectionName` elsewhere in the SDK. Set this or - * `mcpConfigId`, not both. + * value used as `connectionName` elsewhere in the SDK. Used when + * `mcpConfigId` is omitted or empty. * @param params.identifier Your application's unique identifier for the user, - * 1 to 255 characters. - * @param params.expirySeconds Token lifetime in whole seconds. For a - * connection, from 60 (1 minute) to 86400 (24 hours), defaulting to 3600 - * (1 hour) when omitted. + * 1 to 255 characters. For a connection it is checked before the request: + * an empty value throws `identifier is required`. + * @param params.expirySeconds Token lifetime in whole seconds, from 60 + * seconds to 24 hours (86400); 1 hour (3600) by default. * @param params.accessLevel Tools the token can use. `'READ_ONLY'` limits it * to tools annotated read-only: other tools are left out of the tool list * and refused when called. `'FULL'`, or omitting it, exposes every tool the * configuration or connection exposes. * @returns The session `token` and its `expiresAt` time. - * @throws {Error} If `expirySeconds` is not a positive integer, or, for the - * `connectionName` form only, if `connectionName` is not a string or - * `mcpConfigId` is set as well. No request is sent. Calls with - * `mcpConfigId` are not checked beyond `expirySeconds` and behave as they - * always have: a missing or empty ID is sent and rejected by the server. + * @throws {Error} If `expirySeconds` is not a positive integer, or, for a + * connection only, if `connectionName` is not a string or `identifier` is + * empty. No request is sent. Calls with `mcpConfigId` are not checked + * beyond `expirySeconds` and behave as they always have: a missing or empty + * ID is sent and rejected by the server. * @throws {ScalekitNotFoundException} If `connectionName` matches no active * connection. * @throws {ScalekitBadRequestException} If the request is otherwise rejected: @@ -331,34 +332,37 @@ export default class McpClient { } /** - * Returns the request's target field: `keyId` for a connection, otherwise - * `mcpConfigId`. The server requires exactly one of the two, so the other is - * never set. + * Returns the request's target field: `mcpConfigId` for a configuration, + * `keyId` for a connection. The server requires exactly one of the two, so + * the other is never set. * - * Only the `connectionName` form is checked here. Without a non-empty - * `connectionName` the call is a configuration call and `mcpConfigId` is - * passed through as given, unchecked, so existing callers keep their - * behaviour: a missing or empty ID still reaches the server, and a stray - * empty `connectionName` is ignored. + * A non-empty `mcpConfigId` wins, and so does the configuration form when no + * non-empty `connectionName` is given: `mcpConfigId` is then passed through + * as given, unchecked, so existing callers keep their behaviour (a missing or + * empty ID still reaches the server, and `connectionName` is ignored). Only + * the connection form is checked here. */ function sessionTokenTarget( params: CreateMcpSessionTokenParams ): { mcpConfigId?: string } | { keyId: string } { // Read as unknown: plain JavaScript callers bypass the union type. - const { mcpConfigId, connectionName } = params as { + const { mcpConfigId, connectionName, identifier } = params as { mcpConfigId?: unknown; connectionName?: unknown; + identifier?: unknown; }; - if (connectionName == null || connectionName === '') { + const isConnectionCall = + (mcpConfigId == null || mcpConfigId === '') && + connectionName != null && + connectionName !== ''; + if (!isConnectionCall) { return { mcpConfigId: params.mcpConfigId }; } if (typeof connectionName !== 'string') { throw new Error('connectionName must be a non-empty string'); } - if (mcpConfigId != null) { - throw new Error( - 'Set exactly one of mcpConfigId or connectionName, not both' - ); + if (typeof identifier !== 'string' || identifier.trim() === '') { + throw new Error('identifier is required'); } return { keyId: connectionName }; } diff --git a/tests/actions.test.ts b/tests/actions.test.ts index 125c9ee6..dd52f7b3 100644 --- a/tests/actions.test.ts +++ b/tests/actions.test.ts @@ -241,14 +241,16 @@ describe('Actions', () => { ).rejects.toBeInstanceOf(ScalekitNotFoundException); }); - it('should reject both targets before any request', async () => { + it('should use mcpConfigId when both targets are set', async () => { + // mcpConfigId wins, as in v2.19.0: an unknown configuration is rejected + // by the server even though the connection itself is valid. await expect( client.actions.mcp.createSessionToken({ - mcpConfigId: 'cfg_unused', + mcpConfigId: `missing-config-${Date.now()}`, connectionName: GMAIL_CONNECTION_NAME, identifier: GMAIL_IDENTIFIER, - } as any) - ).rejects.toThrow('Set exactly one of mcpConfigId or connectionName'); + }) + ).rejects.toBeInstanceOf(ScalekitServerException); }); }); diff --git a/tests/mcp-session-token.test.ts b/tests/mcp-session-token.test.ts index 1192efff..7750b852 100644 --- a/tests/mcp-session-token.test.ts +++ b/tests/mcp-session-token.test.ts @@ -2,8 +2,9 @@ * McpClient.createSessionToken targets either an MCP configuration * (`mcpConfigId`) or a connection's own MCP server (`connectionName`, sent as * `keyId`). The server requires exactly one of the two; the SDK never sets - * the other field. Only the connectionName form is checked before sending: - * mcpConfigId calls build the same request they always did, unchecked. + * the other field. A non-empty mcpConfigId wins, as it always did; only the + * connectionName form is checked before sending, and mcpConfigId calls build + * the same request they always did, unchecked. * * `accessLevel` is sent only when the caller sets it. An omitted access level * leaves the field empty on the wire, which the server treats as full access, @@ -149,43 +150,110 @@ describe('McpClient.createSessionToken connectionName validation', () => { // Plain JavaScript callers bypass the union type, so these go through `any`. const invalid: Array<[string, Record, string]> = [ [ - 'both targets', - { mcpConfigId: 'cfg_1', connectionName: 'gmail' }, - 'Set exactly one of mcpConfigId or connectionName, not both', + 'a non-string connectionName', + { connectionName: 42, identifier: 'u1' }, + 'connectionName must be a non-empty string', ], [ - 'an empty mcpConfigId alongside connectionName', - { mcpConfigId: '', connectionName: 'gmail' }, - 'Set exactly one of mcpConfigId or connectionName, not both', + 'a non-string connectionName with an empty mcpConfigId', + { mcpConfigId: '', connectionName: 42, identifier: 'u1' }, + 'connectionName must be a non-empty string', ], [ - 'a non-string connectionName', - { connectionName: 42 }, - 'connectionName must be a non-empty string', + 'a missing identifier', + { connectionName: 'gmail' }, + 'identifier is required', + ], + [ + 'an empty identifier', + { connectionName: 'gmail', identifier: '' }, + 'identifier is required', + ], + [ + 'a blank identifier', + { connectionName: 'gmail', identifier: ' ' }, + 'identifier is required', + ], + [ + 'a non-string identifier', + { connectionName: 'gmail', identifier: 7 }, + 'identifier is required', ], ]; it.each(invalid)( 'rejects %s before any request', - async (_label, target, message) => { + async (_label, params, message) => { const { mcp, createMcpSessionToken } = makeMcpClient(); - await expect( - mcp.createSessionToken({ ...target, identifier: 'u1' } as any) - ).rejects.toThrow(message); + await expect(mcp.createSessionToken(params as any)).rejects.toThrow( + message + ); expect(createMcpSessionToken).not.toHaveBeenCalled(); } ); - it('treats a null mcpConfigId as absent', async () => { + it.each([ + ['null', null], + ['empty', ''], + ])( + 'uses connectionName when mcpConfigId is %s', + async (_label, mcpConfigId) => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ + mcpConfigId, + connectionName: 'gmail', + identifier: 'u1', + } as any); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.keyId).toBe('gmail'); + expect(req.mcpConfigId).toBe(''); + } + ); +}); + +describe('McpClient.createSessionToken with both targets', () => { + it('uses mcpConfigId for a spread context that carries connectionName', async () => { + // Compiled and minted a configuration token in v2.19.0: spread + // properties are not excess-checked, so this must keep doing both. + const ctx = { connectionName: 'gmail', identifier: 'u1' }; + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ ...ctx, mcpConfigId: 'cfg_1' }); + expect(createMcpSessionToken).toHaveBeenCalledTimes(1); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req).toEqual( + create(CreateMcpSessionTokenRequestSchema, { + mcpConfigId: 'cfg_1', + identifier: 'u1', + }) + ); + expect(req.keyId).toBe(''); + }); + + it('uses mcpConfigId when both are set and ignores identifier checks', async () => { const { mcp, createMcpSessionToken } = makeMcpClient(); await mcp.createSessionToken({ - mcpConfigId: null, + mcpConfigId: 'cfg_1', connectionName: 'gmail', + identifier: '', + accessLevel: 'READ_ONLY', + }); + const req = createMcpSessionToken.mock.calls[0][0]; + expect(req.mcpConfigId).toBe('cfg_1'); + expect(req.keyId).toBe(''); + expect(req.identifier).toBe(''); + expect(req.accessLevel).toBe('READ_ONLY'); + }); + + it('ignores a non-string connectionName when mcpConfigId is set', async () => { + const { mcp, createMcpSessionToken } = makeMcpClient(); + await mcp.createSessionToken({ + mcpConfigId: 'cfg_1', + connectionName: 42, identifier: 'u1', } as any); const req = createMcpSessionToken.mock.calls[0][0]; - expect(req.keyId).toBe('gmail'); - expect(req.mcpConfigId).toBe(''); + expect(req.mcpConfigId).toBe('cfg_1'); + expect(req.keyId).toBe(''); }); }); @@ -220,6 +288,11 @@ describe('McpClient.createSessionToken mcpConfigId calls are unchecked', () => { 'cfg_1', ], ['only an empty connectionName', { connectionName: '' }, undefined], + [ + 'a non-string mcpConfigId with connectionName', + { mcpConfigId: 42, connectionName: 'gmail' }, + 42, + ], ])('sends the request for %s', async (_label, target, mcpConfigId) => { const { mcp, createMcpSessionToken } = makeMcpClient(); await mcp.createSessionToken({ ...target, identifier: 'u1' } as any); @@ -244,9 +317,8 @@ describe('McpClient.createSessionToken mcpConfigId calls are unchecked', () => { const { mcp, createMcpSessionToken } = makeMcpClient(); await expect( mcp.createSessionToken({ - mcpConfigId: 'cfg_1', - connectionName: 'gmail', - identifier: 'u1', + connectionName: 42, + identifier: '', expirySeconds: 0, } as any) ).rejects.toThrow('expirySeconds must be a positive integer, got 0'); @@ -290,7 +362,7 @@ describe('CreateMcpSessionTokenParams type', () => { // Compile-time checks: ts-jest type-checks this file, so if the union ever // accepts an invalid shape below, the unused expect-error directive fails // the suite. - it('accepts exactly one target', () => { + it('requires a target and an identifier', () => { const byConfig: CreateMcpSessionTokenParams = { mcpConfigId: 'cfg_1', identifier: 'u1', @@ -300,18 +372,37 @@ describe('CreateMcpSessionTokenParams type', () => { identifier: 'u1', accessLevel: 'READ_ONLY', }; - // @ts-expect-error both targets set + // Both targets compile (mcpConfigId wins), as spreads did in v2.19.0. const both: CreateMcpSessionTokenParams = { mcpConfigId: 'cfg_1', connectionName: 'gmail', identifier: 'u1', }; + const ctx = { connectionName: 'gmail', identifier: 'u1' }; + const spread: CreateMcpSessionTokenParams = { ...ctx, mcpConfigId: 'c' }; // @ts-expect-error neither target set const neither: CreateMcpSessionTokenParams = { identifier: 'u1' }; + // @ts-expect-error neither target set, with only optional fields + const neitherOptional: CreateMcpSessionTokenParams = { + identifier: 'u1', + expirySeconds: 60, + }; + // @ts-expect-error identifier is required const noIdentifier: CreateMcpSessionTokenParams = { connectionName: 'g' }; - expect([byConfig, byConnection, both, neither, noIdentifier]).toHaveLength( - 5 - ); + // @ts-expect-error identifier is required with mcpConfigId too + const noIdentifierConfig: CreateMcpSessionTokenParams = { + mcpConfigId: 'c', + }; + expect([ + byConfig, + byConnection, + both, + spread, + neither, + neitherOptional, + noIdentifier, + noIdentifierConfig, + ]).toHaveLength(8); }); });