npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vercel/connex

v0.0.11

Published

SDK for obtaining scoped tokens for third-party services on behalf of apps or users. The runtime authenticates the calling Vercel project via [`@vercel/oidc`](https://www.npmjs.com/package/@vercel/oidc) and exchanges the OIDC token for a Connex-issued cre

Downloads

115

Readme

@vercel/connex

SDK for obtaining scoped tokens for third-party services on behalf of apps or users. The runtime authenticates the calling Vercel project via @vercel/oidc and exchanges the OIDC token for a Connex-issued credential through the Vercel API.

The package ships four entrypoints:

  • @vercel/connex — the core token / authorization SDK (no peer deps beyond @vercel/oidc).
  • @vercel/connex/ash — adapter helpers for the Ash connection runtime. experimental-ash is an optional peer dependency.
  • @vercel/connex/betterauth — a Better Auth genericOAuth provider. better-auth is an optional peer dependency.
  • @vercel/connex/authjs — an Auth.js (NextAuth core) OAuth2Config provider. @auth/core is an optional peer dependency.

Consumers that don't use a given integration never load it.

Install

pnpm add @vercel/connex
# or
npm install @vercel/connex

The package is ESM-only ("type": "module").


@vercel/connex

getToken(clientId, params, options?)

function getToken(
  clientId: string,
  params: ConnexTokenParams,
  options?: ConnexOptions,
): Promise<string>;

Returns a token string for the given Connex client and subject. Wraps getTokenResponse and discards the metadata.

import { getToken } from "@vercel/connex";

const token = await getToken(process.env.CONNEX_CLIENT_ID_LINEAR!, {
  subject: { type: "user", id: "user_123" },
});

getTokenResponse(clientId, params, options?)

function getTokenResponse(
  clientId: string,
  params: ConnexTokenParams,
  options?: ConnexOptions,
): Promise<ConnexTokenResponse>;

Returns the full token response including expiresAt, installationId, tenantId, and externalSubject. Use this when you need the expiration or any of the metadata fields.

Both functions share an in-process LRU cache (max 100 entries) keyed by clientId plus the full params payload. Cached entries are reused until they fall inside the validityBufferMs window before expiration, after which they're refetched.

clientId accepts either the opaque service client key (scl_...) or the human-readable UID (oauth/mcp-linear-app) — both resolve to the same client on the Connex side.

startAuthorization(clientId, params, options?)

function startAuthorization(
  clientId: string,
  params: ConnexTokenParams,
  options?: ConnexAuthorizationOptions,
): Promise<ConnexAuthorizationResponse>;

Begins an interactive (user-facing) authorization flow. Returns a { request, verifier, url } triple; redirect the end user to url, then call getToken / getTokenResponse once Connex signals completion (via the configured webhook or callbackUrl). The verifier is the PKCE verifier — store it alongside the in-flight request so you can correlate the callback.

import { startAuthorization } from "@vercel/connex";

const { url, verifier } = await startAuthorization(
  process.env.CONNEX_CLIENT_ID_LINEAR!,
  { subject: { type: "user", id: "user_123" } },
  { callbackUrl: "https://app.example.com/connex/callback" },
);

callbackUrl must be https:// or http://localhost (for vercel dev). webhook must be https://. Both are validated client-side before the request is sent.

Types

interface ConnexTokenParams {
  subject: { type: "app" } | { type: "user"; id: string; issuer?: string };
  installationId?: string;
  audience?: string[];
  scopes?: string[];
  resources?: string[];
  authorizationDetails?: Array<{ type: string } & Record<string, unknown>>;
  /** Buffer (ms) before expiration to consider the token invalid. Default: 30_000. */
  validityBufferMs?: number;
}

interface ConnexTokenResponse {
  token: string;
  /** Expiration timestamp in ms since epoch. */
  expiresAt: number;
  name?: string;
  installationId?: string;
  tenantId?: string;
  externalSubject?: string;
}

interface ConnexOptions {
  /** Override the OIDC bearer used to authenticate to the Vercel API. */
  vercelToken?: string;
}

interface ConnexAuthorizationOptions {
  vercelToken?: string;
  /** Browser-redirect target after consent. Must be https:// or http://localhost. */
  callbackUrl?: string;
  /** Server-POST callback. Must be https://. */
  webhook?: string;
}

interface ConnexAuthorizationResponse {
  request: string;
  verifier: string;
  url: string;
}

Errors

All exchange / authorization calls reject with typed errors. Catch them by class to distinguish recoverable consent-required cases from terminal failures.

| Class | Meaning | | --------------------------------- | --------------------------------------------------------------------------------------------------------- | | NoValidTokenError | Connex has no valid credential for this subject. For user subjects, recoverable via startAuthorization. | | UserAuthorizationRequiredError | The user has not consented yet (or the previous grant was revoked). Recoverable via startAuthorization. | | ClientInstallationRequiredError | The Connex client is not installed for this team. Terminal — an operator must install the client. |

Any other failure (network, 5xx, etc.) is thrown as a plain Error with the HTTP status and response body in the message.

import {
  getToken,
  NoValidTokenError,
  UserAuthorizationRequiredError,
  ClientInstallationRequiredError,
} from "@vercel/connex";

try {
  await getToken(clientId, { subject: { type: "user", id } });
} catch (err) {
  if (err instanceof UserAuthorizationRequiredError) {
    // Redirect the user through startAuthorization().
  } else if (err instanceof ClientInstallationRequiredError) {
    // Surface "install this Connex client" to an operator.
  } else {
    throw err;
  }
}

@vercel/connex/ash

Adapter helpers for the Ash connection runtime. Importing this subpath requires experimental-ash >= 0.3.0-alpha.19 to be installed in the consumer project.

connex(input)

Builds an Ash AuthorizationDefinition backed by Connex, collapsing the getToken / startAuthorization / completeAuthorization boilerplate that each Connex-backed connection would otherwise repeat.

function connex(
  clientId: string,
): InteractiveAuthorizationDefinition<ConnexAuthorizationState>;
function connex(
  options: AshAuthorizationOptions & { principalType?: "user" },
): InteractiveAuthorizationDefinition<ConnexAuthorizationState>;
function connex(
  options: AshAuthorizationOptions & { principalType: "app" },
): NonInteractiveAuthorizationDefinition;

The shorthand form connex("linear") is equivalent to connex({ clientId: "linear" })principalType defaults to "user", so the helper returns an interactive definition unless you opt into "app" explicitly.

The return type narrows on principalType:

  • "user" (default) → interactive definition. Ash drives the consent flow through its framework-owned webhook; failures map to ConnectionAuthorizationRequiredError so the runtime can prompt the user.
  • "app" → non-interactive definition (getToken only). Failures map to ConnectionAuthorizationFailedError with retryable: false — there's nobody to consent for an app-scoped client.
import { defineMcpClientConnection } from "experimental-ash/connections";
import { connex } from "@vercel/connex/ash";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace — issues, projects, cycles, and comments.",
  auth: connex("oauth/mcp-linear-app"),
});

AshAuthorizationOptions

| Field | Type | Notes | | --------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | clientId | string | Connex client identifier — accepts the opaque scl_... key or the human-readable UID (oauth/mcp-linear-app). | | principalType | "app" \| "user" | Defaults to "user" when omitted. Selects which definition shape is returned. | | tokenParams | Omit<ConnexTokenParams, "subject"> | Forwarded verbatim to every token / authorization call. Use to pin scopes, audience, resources, etc. subject is derived from the framework-resolved principal. | | connexOptions | ConnexOptions | Low-level overrides (e.g. vercelToken). Most callers leave this unset. | | instructions | string | Custom call-to-action shown on connection.authorization_required. Defaults to a message derived from the connection's filename. | | onError | (error: unknown, phase: ConnexAuthorizationPhase) => Error \| undefined | Escape hatch for translating raw errors. Return a replacement Error, or undefined to fall back to the default mapping. |

Supporting types

type AshAuthorizationInput = string | AshAuthorizationOptions;

type ConnexAuthorizationPhase =
  | "getToken"
  | "startAuthorization"
  | "completeAuthorization";

type ConnexAuthorizationState = {
  readonly verifier: string;
  readonly [key: string]: JsonValue;
};

ConnexAuthorizationState is what Ash journals between startAuthorization and completeAuthorization. The index signature satisfies Ash's State extends JsonValue constraint; in practice only verifier is set.

slackChannelCredentials(clientId)

function slackChannelCredentials(clientId: string): SlackChannelCredentials;

Builds SlackChannelCredentials for the Ash Slack channel adapter, backed by a Connex client that stores the workspace's bot token. The returned botToken is a function form, invoked once per inbound webhook so the adapter always picks up a fresh token (rotation, refresh, multi-workspace tenancy are all handled server-side).

Slack bot tokens are app-scoped — one token per workspace install, shared across every end-user — so this helper calls Connex with subject: { type: "app" }. Per-user Slack OAuth is a separate concern.

import { slackRoute } from "experimental-ash/channels/slack";
import { slackChannelCredentials } from "@vercel/connex/ash";

export default slackRoute({
  credentials: slackChannelCredentials("scl_..."),
});

The webhookVerifier is set to vercelOidc(), which authenticates inbound webhooks using the Vercel OIDC token issued for the calling project.


@vercel/connex/betterauth

Better Auth provider for Connex's OAuth2 endpoints. Importing this subpath requires better-auth >= 1.5.0 to be installed in the consumer project.

connex(options)

function connex(options: BetterAuthConnexOptions): GenericOAuthConfig;

Returns a GenericOAuthConfig entry that drops into genericOAuth({ config: [...] }) from better-auth/plugins/generic-oauth. Connex's OAuth client authentication is non-standard — the "client secret" is a Vercel OIDC token that has to be fetched per-request — so the helper overrides getToken and getUserInfo to inject the right Authorization headers.

import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins/generic-oauth";
import { connex } from "@vercel/connex/betterauth";

export const auth = betterAuth({
  plugins: [
    genericOAuth({
      config: [
        connex({
          id: "slack",
          clientId: process.env.CONNEX_CLIENT_ID!,
        }),
      ],
    }),
  ],
});

BetterAuthConnexOptions

| Field | Type | Notes | | -------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- | | id | string | Provider id used by Better Auth's generic OAuth plugin. Surfaces in the sign-in URL and account rows. | | clientId | string | Connex client identifier — accepts scl_... or the human-readable UID. | | scopes | readonly string[] | Scopes to request. offline_access is added automatically. Defaults to ["*"]. | | getVercelOidcToken | () => Promise<string> | Override the OIDC token fetcher. Defaults to getVercelOidcToken from @vercel/oidc. |

When the OIDC token is readable, the helper scopes the Connex consent UI to the deployment's Vercel team (?teamId=... on the authorization URL); outside Vercel it falls back to the unqualified URL.


@vercel/connex/authjs

Auth.js (NextAuth core) provider for Connex's OAuth2 endpoints. Importing this subpath requires @auth/core >= 0.37.0 to be installed in the consumer project.

connex(options)

function connex(options: AuthJsConnexOptions): OAuth2Config<ConnexProfile>;

Returns an OAuth2Config provider entry for AuthConfig.providers. Connex's token endpoint expects the Vercel OIDC token in place of a static client secret, so the helper declares token_endpoint_auth_method: "none" and injects the real Authorization headers via Auth.js's customFetch symbol on both the token and userinfo endpoints.

import type { AuthConfig } from "@auth/core";
import { connex } from "@vercel/connex/authjs";

export const authConfig: AuthConfig = {
  providers: [
    connex({
      id: "slack",
      name: "Slack",
      clientId: process.env.CONNEX_CLIENT_ID!,
    }),
  ],
};

AuthJsConnexOptions

| Field | Type | Notes | | -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------- | | id | string | Provider id surfaced by Auth.js. Becomes the path segment in callback URLs and the provider field on accounts. | | name | string | Human-readable display name used by Auth.js sign-in UIs. | | clientId | string | Connex client identifier — accepts scl_... or the human-readable UID. | | scopes | readonly string[] | Scopes to request. offline_access is added automatically. Defaults to ["*"]. | | getVercelOidcToken | () => Promise<string> | Override the OIDC token fetcher. Defaults to getVercelOidcToken from @vercel/oidc. |

ConnexProfile

interface ConnexProfile {
  sub: string;
  email?: string;
  email_verified?: boolean;
  name?: string;
  picture?: string;
}

Userinfo payload returned by Connex's OIDC userinfo endpoint. The helper maps it to an Auth.js user via profile(); override that callback in the provider config if you need to surface additional fields.