@vercel/connect 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -6
- package/dist/ai-sdk/index.d.ts +52 -0
- package/dist/ai-sdk/index.js +52 -0
- package/dist/{ash → eve}/connect-oauth.d.ts +4 -4
- package/dist/{ash → eve}/connect-oauth.js +2 -2
- package/dist/{ash → eve}/connection-authorization.d.ts +29 -42
- package/dist/{ash → eve}/connection-authorization.js +17 -18
- package/dist/eve/github-credentials.d.ts +24 -0
- package/dist/eve/github-credentials.js +20 -0
- package/dist/eve/index.d.ts +13 -0
- package/dist/eve/index.js +13 -0
- package/dist/eve/linear-credentials.d.ts +22 -0
- package/dist/eve/linear-credentials.js +19 -0
- package/dist/{ash → eve}/slack-credentials.d.ts +3 -3
- package/dist/{ash → eve}/slack-credentials.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/connect-auth-provider.d.ts +99 -0
- package/dist/mcp/connect-auth-provider.js +148 -0
- package/dist/mcp/index.d.ts +33 -0
- package/dist/mcp/index.js +33 -0
- package/dist/token.d.ts +8 -0
- package/dist/token.js +17 -0
- package/package.json +30 -9
- package/dist/ash/index.d.ts +0 -11
- package/dist/ash/index.js +0 -11
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
SDK for obtaining scoped tokens for third-party services on behalf of apps or users. Authenticates the calling Vercel project via [`@vercel/oidc`](https://www.npmjs.com/package/@vercel/oidc) and exchanges the OIDC token for a Vercel Connect-issued credential.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Six entrypoints, all ESM:
|
|
6
6
|
|
|
7
7
|
- `@vercel/connect` — core token / authorization SDK
|
|
8
|
-
- `@vercel/connect/
|
|
8
|
+
- `@vercel/connect/ai-sdk` — [Vercel AI SDK](https://ai-sdk.dev) glue: re-exports `connectAuthProvider` for MCP transports (optional peers: `ai`, `@ai-sdk/mcp`)
|
|
9
|
+
- `@vercel/connect/mcp` — canonical MCP-spec `OAuthClientProvider` for any MCP client (optional peer: `@ai-sdk/mcp`)
|
|
10
|
+
- `@vercel/connect/eve` — adapter helpers for [Eve](https://github.com/vercel/eve) connections (optional peer: `eve`)
|
|
9
11
|
- `@vercel/connect/betterauth` — [Better Auth](https://www.better-auth.com/) `genericOAuth` provider (optional peer: `better-auth`)
|
|
10
12
|
- `@vercel/connect/authjs` — [Auth.js](https://authjs.dev/) `OAuth2Config` provider (optional peer: `@auth/core`)
|
|
11
13
|
|
|
@@ -27,11 +29,50 @@ const token = await getToken(process.env.CONNECTOR_LINEAR!, {
|
|
|
27
29
|
});
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
###
|
|
32
|
+
### Vercel AI SDK + MCP
|
|
31
33
|
|
|
32
34
|
```ts
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
36
|
+
import { streamText } from 'ai';
|
|
37
|
+
import {
|
|
38
|
+
connectAuthProvider,
|
|
39
|
+
ConsentRequiredError,
|
|
40
|
+
} from '@vercel/connect/ai-sdk';
|
|
41
|
+
|
|
42
|
+
const mcp = await createMCPClient({
|
|
43
|
+
transport: {
|
|
44
|
+
type: 'http',
|
|
45
|
+
url: 'https://mcp.linear.app',
|
|
46
|
+
authProvider: connectAuthProvider('oauth/linear', {
|
|
47
|
+
subject: { type: 'user', id: 'user_123' },
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const result = await streamText({
|
|
54
|
+
model: 'openai/gpt-5.4',
|
|
55
|
+
tools: await mcp.tools(),
|
|
56
|
+
prompt,
|
|
57
|
+
});
|
|
58
|
+
return result.toUIMessageStreamResponse();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err instanceof ConsentRequiredError) return Response.redirect(err.url);
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Tool-call approval (Human-in-the-Loop) is independent of Connect — use the AI
|
|
66
|
+
SDK's `toolApproval` option or `wrapMcpTools` from `@ai-sdk/policy-opa`.
|
|
67
|
+
|
|
68
|
+
Non-AI-SDK MCP clients (the official MCP TypeScript SDK, Mastra, etc.)
|
|
69
|
+
can import the same `connectAuthProvider` from `@vercel/connect/mcp`.
|
|
70
|
+
|
|
71
|
+
### Eve
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { defineMcpClientConnection } from 'eve/connections';
|
|
75
|
+
import { connect } from '@vercel/connect/eve';
|
|
35
76
|
|
|
36
77
|
export default defineMcpClientConnection({
|
|
37
78
|
url: 'https://mcp.linear.app/sse',
|
|
@@ -56,4 +97,4 @@ import { connect } from '@vercel/connect/authjs';
|
|
|
56
97
|
const providers = [connect({ connector: 'linear' })];
|
|
57
98
|
```
|
|
58
99
|
|
|
59
|
-
See the source under `src/` for the full API (additional helpers like `getTokenResponse`, `startAuthorization`, typed error classes, and per-adapter options).
|
|
100
|
+
See the source under `src/` for the full API (additional helpers like `revokeToken`, `getTokenResponse`, `startAuthorization`, typed error classes, and per-adapter options).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/ai-sdk` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the MCP-spec `connectAuthProvider` (and its consent
|
|
5
|
+
* types) so AI SDK users have a single, ergonomic import. The
|
|
6
|
+
* provider plugs straight into `@ai-sdk/mcp`'s `createMCPClient`
|
|
7
|
+
* `authProvider` and works with AI SDK v6 and v7.
|
|
8
|
+
*
|
|
9
|
+
* Tool-call approval (Human-in-the-Loop) is intentionally not
|
|
10
|
+
* provided here — it is independent of Connect and already covered by
|
|
11
|
+
* the AI SDK's own `toolApproval` primitive (and `wrapMcpTools` in
|
|
12
|
+
* `@ai-sdk/policy-opa`). See `docs/ai-sdk-mcp-integration.md`.
|
|
13
|
+
*
|
|
14
|
+
* Both `ai` and `@ai-sdk/mcp` are optional peer dependencies:
|
|
15
|
+
* importing this entrypoint requires them to be installed in the
|
|
16
|
+
* consumer project, but the rest of `@vercel/connect` works without
|
|
17
|
+
* them.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { createMCPClient } from '@ai-sdk/mcp';
|
|
21
|
+
* import { streamText } from 'ai';
|
|
22
|
+
* import {
|
|
23
|
+
* connectAuthProvider,
|
|
24
|
+
* ConsentRequiredError,
|
|
25
|
+
* } from '@vercel/connect/ai-sdk';
|
|
26
|
+
*
|
|
27
|
+
* const mcpClient = await createMCPClient({
|
|
28
|
+
* transport: {
|
|
29
|
+
* type: 'http',
|
|
30
|
+
* url: 'https://mcp.linear.app',
|
|
31
|
+
* authProvider: connectAuthProvider('oauth/linear', {
|
|
32
|
+
* subject: { type: 'user', id: userId },
|
|
33
|
+
* scopes: ['read'],
|
|
34
|
+
* }),
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* try {
|
|
39
|
+
* const result = await streamText({
|
|
40
|
+
* model: 'openai/gpt-5.4',
|
|
41
|
+
* tools: await mcpClient.tools(),
|
|
42
|
+
* prompt,
|
|
43
|
+
* });
|
|
44
|
+
* return result.toUIMessageStreamResponse();
|
|
45
|
+
* } catch (err) {
|
|
46
|
+
* if (err instanceof ConsentRequiredError) return Response.redirect(err.url);
|
|
47
|
+
* throw err;
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export { connectAuthProvider, ConsentRequiredError, type ConnectAuthProviderOptions, type ConsentChallenge, } from '../mcp/connect-auth-provider.js';
|
|
52
|
+
export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/ai-sdk` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the MCP-spec `connectAuthProvider` (and its consent
|
|
5
|
+
* types) so AI SDK users have a single, ergonomic import. The
|
|
6
|
+
* provider plugs straight into `@ai-sdk/mcp`'s `createMCPClient`
|
|
7
|
+
* `authProvider` and works with AI SDK v6 and v7.
|
|
8
|
+
*
|
|
9
|
+
* Tool-call approval (Human-in-the-Loop) is intentionally not
|
|
10
|
+
* provided here — it is independent of Connect and already covered by
|
|
11
|
+
* the AI SDK's own `toolApproval` primitive (and `wrapMcpTools` in
|
|
12
|
+
* `@ai-sdk/policy-opa`). See `docs/ai-sdk-mcp-integration.md`.
|
|
13
|
+
*
|
|
14
|
+
* Both `ai` and `@ai-sdk/mcp` are optional peer dependencies:
|
|
15
|
+
* importing this entrypoint requires them to be installed in the
|
|
16
|
+
* consumer project, but the rest of `@vercel/connect` works without
|
|
17
|
+
* them.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { createMCPClient } from '@ai-sdk/mcp';
|
|
21
|
+
* import { streamText } from 'ai';
|
|
22
|
+
* import {
|
|
23
|
+
* connectAuthProvider,
|
|
24
|
+
* ConsentRequiredError,
|
|
25
|
+
* } from '@vercel/connect/ai-sdk';
|
|
26
|
+
*
|
|
27
|
+
* const mcpClient = await createMCPClient({
|
|
28
|
+
* transport: {
|
|
29
|
+
* type: 'http',
|
|
30
|
+
* url: 'https://mcp.linear.app',
|
|
31
|
+
* authProvider: connectAuthProvider('oauth/linear', {
|
|
32
|
+
* subject: { type: 'user', id: userId },
|
|
33
|
+
* scopes: ['read'],
|
|
34
|
+
* }),
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* try {
|
|
39
|
+
* const result = await streamText({
|
|
40
|
+
* model: 'openai/gpt-5.4',
|
|
41
|
+
* tools: await mcpClient.tools(),
|
|
42
|
+
* prompt,
|
|
43
|
+
* });
|
|
44
|
+
* return result.toUIMessageStreamResponse();
|
|
45
|
+
* } catch (err) {
|
|
46
|
+
* if (err instanceof ConsentRequiredError) return Response.redirect(err.url);
|
|
47
|
+
* throw err;
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export { connectAuthProvider, ConsentRequiredError, } from '../mcp/connect-auth-provider.js';
|
|
52
|
+
export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, } from '../token.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AuthFn, type VerifyOidcConfig } from '
|
|
1
|
+
import { type AuthFn, type VerifyOidcConfig } from 'eve/channels/auth';
|
|
2
2
|
export declare const CONNECT_OAUTH_ISSUER = "https://connect.vercel.com";
|
|
3
3
|
export type ConnectOAuthEnvironment = 'production' | 'preview' | 'development';
|
|
4
4
|
export type ConnectOAuthAudienceEnvironment = ConnectOAuthEnvironment | '*';
|
|
@@ -23,7 +23,7 @@ export interface ConnectOAuthOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
readonly environment?: ConnectOAuthAudienceEnvironment;
|
|
25
25
|
/**
|
|
26
|
-
* Optional gateway session id (`sub`) matchers. Patterns use
|
|
26
|
+
* Optional gateway session id (`sub`) matchers. Patterns use Eve's
|
|
27
27
|
* IAM-style `*` wildcard matching.
|
|
28
28
|
*/
|
|
29
29
|
readonly subjects?: readonly string[];
|
|
@@ -49,11 +49,11 @@ export interface ConnectOAuthOptions {
|
|
|
49
49
|
* `https://connect.vercel.com/.well-known/openid-configuration`.
|
|
50
50
|
*/
|
|
51
51
|
readonly discoveryUrl?: string;
|
|
52
|
-
/** Clock skew in seconds. Defaults to
|
|
52
|
+
/** Clock skew in seconds. Defaults to Eve's OIDC verifier default. */
|
|
53
53
|
readonly clockSkewSeconds?: number;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Returns an
|
|
56
|
+
* Returns an Eve route auth callback for Vercel Connect OAuth gateway
|
|
57
57
|
* access tokens.
|
|
58
58
|
*
|
|
59
59
|
* The accepted token must be a bearer JWT issued by
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { extractBearerToken, verifyOidc, } from '
|
|
1
|
+
import { extractBearerToken, verifyOidc, } from 'eve/channels/auth';
|
|
2
2
|
export const CONNECT_OAUTH_ISSUER = 'https://connect.vercel.com';
|
|
3
3
|
/**
|
|
4
|
-
* Returns an
|
|
4
|
+
* Returns an Eve route auth callback for Vercel Connect OAuth gateway
|
|
5
5
|
* access tokens.
|
|
6
6
|
*
|
|
7
7
|
* The accepted token must be a bearer JWT issued by
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Eve adapter helper for `@vercel/connect`.
|
|
3
3
|
*
|
|
4
4
|
* {@link connect} turns a Vercel Connect OAuth connector id plus a
|
|
5
|
-
* principal type into a ready-made
|
|
5
|
+
* principal type into a ready-made Eve
|
|
6
6
|
* {@link AuthorizationDefinition} that the connection runtime
|
|
7
7
|
* consumes directly. The helper collapses the ~100 lines of
|
|
8
8
|
* `getToken` / `startAuthorization` / `completeAuthorization`
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* to a single call:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { defineMcpClientConnection } from "
|
|
14
|
-
* import { connect } from "@vercel/connect/
|
|
13
|
+
* import { defineMcpClientConnection } from "eve/connections";
|
|
14
|
+
* import { connect } from "@vercel/connect/eve";
|
|
15
15
|
*
|
|
16
16
|
* export default defineMcpClientConnection({
|
|
17
17
|
* url: "https://mcp.linear.app/sse",
|
|
@@ -22,34 +22,21 @@
|
|
|
22
22
|
*
|
|
23
23
|
* # Module layout
|
|
24
24
|
*
|
|
25
|
-
* This entrypoint is exposed at the `@vercel/connect/
|
|
26
|
-
* consumers that don't use
|
|
25
|
+
* This entrypoint is exposed at the `@vercel/connect/eve` subpath so
|
|
26
|
+
* consumers that don't use Eve never load it. `eve` is
|
|
27
27
|
* declared as an optional peer dependency: importing
|
|
28
|
-
* `@vercel/connect/
|
|
28
|
+
* `@vercel/connect/eve` requires Eve to be installed in the consumer
|
|
29
29
|
* project, but the rest of `@vercel/connect` works without it.
|
|
30
30
|
*/
|
|
31
|
-
import { type ConnectionPrincipal, type InteractiveAuthorizationDefinition, type
|
|
31
|
+
import { type ConnectionPrincipal, type InteractiveAuthorizationDefinition, type NonInteractiveAuthorizationDefinition } from 'eve/connections';
|
|
32
32
|
import type { ConnectOptions, ConnectTokenParams, ConnectTokenSubject } from '../token.js';
|
|
33
33
|
/**
|
|
34
|
-
* Authorization phase passed to {@link
|
|
34
|
+
* Authorization phase passed to {@link EveAuthorizationOptions.onError}
|
|
35
35
|
* so consumers can branch their error translation per callback.
|
|
36
36
|
*/
|
|
37
37
|
export type ConnectAuthorizationPhase = 'getToken' | 'startAuthorization' | 'completeAuthorization';
|
|
38
|
-
/**
|
|
39
|
-
* State journaled by Ash between
|
|
40
|
-
* {@link InteractiveAuthorizationDefinition.startAuthorization} and
|
|
41
|
-
* {@link InteractiveAuthorizationDefinition.completeAuthorization}.
|
|
42
|
-
*
|
|
43
|
-
* Currently just the PKCE verifier. The index signature exists to
|
|
44
|
-
* satisfy Ash's `State extends JsonValue` constraint; in practice
|
|
45
|
-
* the only key the helper produces or reads is `verifier`.
|
|
46
|
-
*/
|
|
47
|
-
export type ConnectAuthorizationState = {
|
|
48
|
-
readonly verifier: string;
|
|
49
|
-
readonly [key: string]: JsonValue;
|
|
50
|
-
};
|
|
51
38
|
/** Options accepted by {@link connect}. */
|
|
52
|
-
export interface
|
|
39
|
+
export interface EveAuthorizationOptions {
|
|
53
40
|
/**
|
|
54
41
|
* Vercel Connect OAuth connector identifier. Accepts either the
|
|
55
42
|
* opaque service connector key (`scl_...`) or the human-readable
|
|
@@ -64,9 +51,9 @@ export interface AshAuthorizationOptions {
|
|
|
64
51
|
* on this choice:
|
|
65
52
|
*
|
|
66
53
|
* - `"user"` → full interactive OAuth definition with `getToken`,
|
|
67
|
-
* `startAuthorization`, and `completeAuthorization`.
|
|
54
|
+
* `startAuthorization`, and `completeAuthorization`. Eve will
|
|
68
55
|
* drive a consent flow through its framework-owned webhook.
|
|
69
|
-
* - `"app"` → non-interactive definition with `getToken` only.
|
|
56
|
+
* - `"app"` → non-interactive definition with `getToken` only. Eve
|
|
70
57
|
* never runs a consent flow for app-scoped connectors; a failure
|
|
71
58
|
* to fetch the token surfaces as a terminal authorization
|
|
72
59
|
* failure so the channel can prompt an operator to install the
|
|
@@ -83,7 +70,7 @@ export interface AshAuthorizationOptions {
|
|
|
83
70
|
*/
|
|
84
71
|
readonly tokenParams?: Omit<ConnectTokenParams, 'subject'>;
|
|
85
72
|
/**
|
|
86
|
-
* Override how
|
|
73
|
+
* Override how Eve's framework-resolved principal is mapped to a
|
|
87
74
|
* Vercel Connect token subject. When omitted, app principals map to
|
|
88
75
|
* `{ type: "app" }` and user principals map to
|
|
89
76
|
* `{ type: "user", id, issuer }`.
|
|
@@ -97,14 +84,14 @@ export interface AshAuthorizationOptions {
|
|
|
97
84
|
readonly connectOptions?: ConnectOptions;
|
|
98
85
|
/**
|
|
99
86
|
* Custom call-to-action rendered on the
|
|
100
|
-
* `connection.authorization_required` event. When omitted,
|
|
87
|
+
* `connection.authorization_required` event. When omitted, Eve
|
|
101
88
|
* fills in `Authorize <ConnectionName> in your browser to continue.`
|
|
102
89
|
* from the connection's filename.
|
|
103
90
|
*/
|
|
104
91
|
readonly instructions?: string;
|
|
105
92
|
/**
|
|
106
93
|
* Escape hatch for turning an unexpected Vercel Connect / network
|
|
107
|
-
* error into an
|
|
94
|
+
* error into an Eve-recognizable error. Called once per failure
|
|
108
95
|
* with the raw error and the phase that produced it.
|
|
109
96
|
*
|
|
110
97
|
* Return a new `Error` to replace the helper's default translation,
|
|
@@ -115,7 +102,7 @@ export interface AshAuthorizationOptions {
|
|
|
115
102
|
readonly onError?: (error: unknown, phase: ConnectAuthorizationPhase) => Error | undefined;
|
|
116
103
|
}
|
|
117
104
|
/** Input accepted by {@link connect}. */
|
|
118
|
-
export type
|
|
105
|
+
export type EveAuthorizationInput = string | EveAuthorizationOptions;
|
|
119
106
|
/**
|
|
120
107
|
* Structurally-readable marker exposed on every {@link connect} return
|
|
121
108
|
* value so downstream tooling can detect Vercel Connect-backed
|
|
@@ -139,35 +126,35 @@ export interface VercelConnectMetadata {
|
|
|
139
126
|
readonly connector: string;
|
|
140
127
|
}
|
|
141
128
|
/**
|
|
142
|
-
* Augments the standard
|
|
129
|
+
* Augments the standard Eve authorization shape with the
|
|
143
130
|
* {@link VercelConnectMetadata} marker, narrowed to whichever flavour
|
|
144
131
|
* `connect()` produces.
|
|
145
132
|
*/
|
|
146
|
-
export type
|
|
133
|
+
export type EveConnectAuthorizationDefinition<TAuthorization extends InteractiveAuthorizationDefinition | NonInteractiveAuthorizationDefinition> = TAuthorization & {
|
|
147
134
|
readonly vercelConnect: VercelConnectMetadata;
|
|
148
135
|
};
|
|
149
136
|
/**
|
|
150
|
-
* Builds an
|
|
137
|
+
* Builds an Eve {@link AuthorizationDefinition} backed by Vercel
|
|
151
138
|
* Connect. The return type narrows based on
|
|
152
|
-
* {@link
|
|
139
|
+
* {@link EveAuthorizationOptions.principalType}:
|
|
153
140
|
*
|
|
154
141
|
* - omitted or `principalType: "user"` returns an
|
|
155
|
-
* {@link InteractiveAuthorizationDefinition};
|
|
142
|
+
* {@link InteractiveAuthorizationDefinition}; Eve drives a consent
|
|
156
143
|
* flow through its framework-owned webhook.
|
|
157
144
|
* - `principalType: "app"` returns a
|
|
158
|
-
* {@link NonInteractiveAuthorizationDefinition};
|
|
145
|
+
* {@link NonInteractiveAuthorizationDefinition}; Eve never runs a
|
|
159
146
|
* consent flow for app-scoped connectors.
|
|
160
147
|
*
|
|
161
148
|
* Every returned definition also carries a {@link VercelConnectMetadata}
|
|
162
|
-
* marker on its `vercelConnect` field so downstream tooling (
|
|
149
|
+
* marker on its `vercelConnect` field so downstream tooling (Eve
|
|
163
150
|
* compiler, dashboards) can detect Vercel Connect-backed connections
|
|
164
151
|
* without inspecting closure state.
|
|
165
152
|
*/
|
|
166
|
-
export declare function connect(connector: string):
|
|
167
|
-
export declare function connect(options:
|
|
153
|
+
export declare function connect(connector: string): EveConnectAuthorizationDefinition<InteractiveAuthorizationDefinition>;
|
|
154
|
+
export declare function connect(options: EveAuthorizationOptions & {
|
|
168
155
|
readonly principalType?: 'user';
|
|
169
|
-
}):
|
|
170
|
-
export declare function connect(options:
|
|
156
|
+
}): EveConnectAuthorizationDefinition<InteractiveAuthorizationDefinition>;
|
|
157
|
+
export declare function connect(options: EveAuthorizationOptions & {
|
|
171
158
|
readonly principalType: 'app';
|
|
172
|
-
}):
|
|
173
|
-
export declare function connect(options:
|
|
159
|
+
}): EveConnectAuthorizationDefinition<NonInteractiveAuthorizationDefinition>;
|
|
160
|
+
export declare function connect(options: EveAuthorizationInput): EveConnectAuthorizationDefinition<InteractiveAuthorizationDefinition | NonInteractiveAuthorizationDefinition>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Eve adapter helper for `@vercel/connect`.
|
|
3
3
|
*
|
|
4
4
|
* {@link connect} turns a Vercel Connect OAuth connector id plus a
|
|
5
|
-
* principal type into a ready-made
|
|
5
|
+
* principal type into a ready-made Eve
|
|
6
6
|
* {@link AuthorizationDefinition} that the connection runtime
|
|
7
7
|
* consumes directly. The helper collapses the ~100 lines of
|
|
8
8
|
* `getToken` / `startAuthorization` / `completeAuthorization`
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* to a single call:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { defineMcpClientConnection } from "
|
|
14
|
-
* import { connect } from "@vercel/connect/
|
|
13
|
+
* import { defineMcpClientConnection } from "eve/connections";
|
|
14
|
+
* import { connect } from "@vercel/connect/eve";
|
|
15
15
|
*
|
|
16
16
|
* export default defineMcpClientConnection({
|
|
17
17
|
* url: "https://mcp.linear.app/sse",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
*
|
|
23
23
|
* # Module layout
|
|
24
24
|
*
|
|
25
|
-
* This entrypoint is exposed at the `@vercel/connect/
|
|
26
|
-
* consumers that don't use
|
|
25
|
+
* This entrypoint is exposed at the `@vercel/connect/eve` subpath so
|
|
26
|
+
* consumers that don't use Eve never load it. `eve` is
|
|
27
27
|
* declared as an optional peer dependency: importing
|
|
28
|
-
* `@vercel/connect/
|
|
28
|
+
* `@vercel/connect/eve` requires Eve to be installed in the consumer
|
|
29
29
|
* project, but the rest of `@vercel/connect` works without it.
|
|
30
30
|
*/
|
|
31
|
-
import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError, } from '
|
|
31
|
+
import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError, } from 'eve/connections';
|
|
32
32
|
import { startAuthorization } from '../authorization.js';
|
|
33
33
|
import { ConnectorInstallationRequiredError, getTokenResponse, NoValidTokenError, UserAuthorizationRequiredError, } from '../token.js';
|
|
34
34
|
export function connect(input) {
|
|
@@ -59,7 +59,7 @@ function buildInteractiveDefinition(options) {
|
|
|
59
59
|
},
|
|
60
60
|
async startAuthorization({ principal, callbackUrl, webhook, }) {
|
|
61
61
|
try {
|
|
62
|
-
//
|
|
62
|
+
// Eve's `webhook` parameter is semantically a browser-redirect
|
|
63
63
|
// target — the orchestrator mints it via `createWebhook({
|
|
64
64
|
// respondWith: buildAuthorizationCompletePage() })` so the
|
|
65
65
|
// user lands on a friendly "you can close this tab" page after
|
|
@@ -74,9 +74,9 @@ function buildInteractiveDefinition(options) {
|
|
|
74
74
|
// `webhook:` (server-POST) field, even though it would
|
|
75
75
|
// survive the user closing the consent tab right after IdP
|
|
76
76
|
// callback. That mode shows the user Vercel Connect's
|
|
77
|
-
// generic "close this window" page instead of
|
|
77
|
+
// generic "close this window" page instead of Eve's branded
|
|
78
78
|
// landing page, and the helper would need to grow
|
|
79
|
-
// protocol-aware logic that diverges from the simple "
|
|
79
|
+
// protocol-aware logic that diverges from the simple "Eve
|
|
80
80
|
// mints one URL, Vercel Connect redirects there" mental
|
|
81
81
|
// model. Revisit if tab-close timeouts become a real problem
|
|
82
82
|
// in production.
|
|
@@ -96,7 +96,6 @@ function buildInteractiveDefinition(options) {
|
|
|
96
96
|
? { instructions: options.instructions }
|
|
97
97
|
: null),
|
|
98
98
|
},
|
|
99
|
-
state: { verifier: response.verifier },
|
|
100
99
|
};
|
|
101
100
|
}
|
|
102
101
|
catch (error) {
|
|
@@ -142,11 +141,11 @@ function principalToSubject(principal) {
|
|
|
142
141
|
return { type: 'user', id: principal.id, issuer: principal.issuer };
|
|
143
142
|
}
|
|
144
143
|
/**
|
|
145
|
-
* Translates raw Vercel Connect errors into
|
|
144
|
+
* Translates raw Vercel Connect errors into Eve's public
|
|
146
145
|
* {@link ConnectionAuthorizationRequiredError} /
|
|
147
|
-
* {@link ConnectionAuthorizationFailedError} classes.
|
|
146
|
+
* {@link ConnectionAuthorizationFailedError} classes. Eve discriminates
|
|
148
147
|
* on `err.name` (not `instanceof`), so even if the consumer's bundle
|
|
149
|
-
* loads a different copy of `
|
|
148
|
+
* loads a different copy of `eve` than this helper, the
|
|
150
149
|
* runtime still recognizes the throw.
|
|
151
150
|
*/
|
|
152
151
|
function translate(error, phase, options) {
|
|
@@ -156,7 +155,7 @@ function translate(error, phase, options) {
|
|
|
156
155
|
// `UserAuthorizationRequiredError` and `NoValidTokenError` both
|
|
157
156
|
// mean "Vercel Connect has no valid credential for this principal
|
|
158
157
|
// yet". For interactive (user) connectors that's recoverable via
|
|
159
|
-
// a consent flow;
|
|
158
|
+
// a consent flow; Eve will see the `Required` throw and drive
|
|
160
159
|
// `startAuthorization`. For app connectors it's terminal — there
|
|
161
160
|
// is nobody to consent — so we surface `Failed` with
|
|
162
161
|
// `retryable: false`.
|
|
@@ -170,7 +169,7 @@ function translate(error, phase, options) {
|
|
|
170
169
|
});
|
|
171
170
|
}
|
|
172
171
|
if (phase === 'completeAuthorization') {
|
|
173
|
-
// The consent leg reported success (
|
|
172
|
+
// The consent leg reported success (Eve would not call us
|
|
174
173
|
// otherwise) but Vercel Connect still says the user is
|
|
175
174
|
// unauthorized. Default to retryable so the model can
|
|
176
175
|
// re-prompt; most cases (network blip, replay) resolve on
|
|
@@ -193,7 +192,7 @@ function translate(error, phase, options) {
|
|
|
193
192
|
retryable: false,
|
|
194
193
|
});
|
|
195
194
|
}
|
|
196
|
-
// Every other error is re-thrown verbatim.
|
|
195
|
+
// Every other error is re-thrown verbatim. Eve treats an unknown
|
|
197
196
|
// throw from `completeAuthorization` as a retryable failure, so the
|
|
198
197
|
// default behavior stays intuitive.
|
|
199
198
|
return error instanceof Error ? error : new Error(String(error));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GitHubChannelCredentials } from 'eve/channels/github';
|
|
2
|
+
import { type ConnectOptions, type ConnectTokenParams } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Token parameters accepted by {@link connectGitHubCredentials}.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors {@link ConnectTokenParams} from `@vercel/connect`, minus
|
|
7
|
+
* `subject` — GitHub installation tokens are app-scoped, so `subject`
|
|
8
|
+
* is pinned to `{ type: "app" }` by this helper and cannot be
|
|
9
|
+
* overridden.
|
|
10
|
+
*/
|
|
11
|
+
export type ConnectGitHubCredentialsParams = Omit<ConnectTokenParams, 'subject'>;
|
|
12
|
+
/**
|
|
13
|
+
* Build {@link GitHubChannelCredentials} backed by a Vercel Connect
|
|
14
|
+
* connector that stores a GitHub installation access token.
|
|
15
|
+
*
|
|
16
|
+
* Eve uses `installationToken` directly for authenticated GitHub API
|
|
17
|
+
* calls and skips its native GitHub App JWT exchange. The token is a
|
|
18
|
+
* function form so rotation, refresh, and multi-installation tenancy
|
|
19
|
+
* stay delegated to Vercel Connect.
|
|
20
|
+
*
|
|
21
|
+
* The webhook verifier accepts Connect-forwarded webhooks authenticated
|
|
22
|
+
* with Vercel OIDC instead of GitHub's webhook secret.
|
|
23
|
+
*/
|
|
24
|
+
export declare function connectGitHubCredentials(connector: string, params?: ConnectGitHubCredentialsParams, options?: ConnectOptions): GitHubChannelCredentials;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { vercelOidc } from 'eve/channels/auth';
|
|
2
|
+
import { getToken, } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build {@link GitHubChannelCredentials} backed by a Vercel Connect
|
|
5
|
+
* connector that stores a GitHub installation access token.
|
|
6
|
+
*
|
|
7
|
+
* Eve uses `installationToken` directly for authenticated GitHub API
|
|
8
|
+
* calls and skips its native GitHub App JWT exchange. The token is a
|
|
9
|
+
* function form so rotation, refresh, and multi-installation tenancy
|
|
10
|
+
* stay delegated to Vercel Connect.
|
|
11
|
+
*
|
|
12
|
+
* The webhook verifier accepts Connect-forwarded webhooks authenticated
|
|
13
|
+
* with Vercel OIDC instead of GitHub's webhook secret.
|
|
14
|
+
*/
|
|
15
|
+
export function connectGitHubCredentials(connector, params = {}, options) {
|
|
16
|
+
return {
|
|
17
|
+
installationToken: () => getToken(connector, { ...params, subject: { type: 'app' } }, options),
|
|
18
|
+
webhookVerifier: vercelOidc(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/eve` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Holds Eve-specific helpers that adapt the Vercel Connect SDK to
|
|
5
|
+
* Eve's connection runtime. Each helper lives in its own module;
|
|
6
|
+
* this barrel re-exports the public API so consumers import
|
|
7
|
+
* everything from `@vercel/connect/eve`.
|
|
8
|
+
*/
|
|
9
|
+
export { connect, type EveAuthorizationInput, type EveAuthorizationOptions, type EveConnectAuthorizationDefinition, type ConnectAuthorizationPhase, type VercelConnectMetadata, } from './connection-authorization.js';
|
|
10
|
+
export { CONNECT_OAUTH_ISSUER, connectOAuth, type ConnectOAuthAudienceEnvironment, type ConnectOAuthEnvironment, type ConnectOAuthOptions, } from './connect-oauth.js';
|
|
11
|
+
export { connectGitHubCredentials, type ConnectGitHubCredentialsParams, } from './github-credentials.js';
|
|
12
|
+
export { connectLinearCredentials, type ConnectLinearCredentialsParams, } from './linear-credentials.js';
|
|
13
|
+
export { connectSlackCredentials, type ConnectSlackCredentialsParams, } from './slack-credentials.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/eve` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Holds Eve-specific helpers that adapt the Vercel Connect SDK to
|
|
5
|
+
* Eve's connection runtime. Each helper lives in its own module;
|
|
6
|
+
* this barrel re-exports the public API so consumers import
|
|
7
|
+
* everything from `@vercel/connect/eve`.
|
|
8
|
+
*/
|
|
9
|
+
export { connect, } from './connection-authorization.js';
|
|
10
|
+
export { CONNECT_OAUTH_ISSUER, connectOAuth, } from './connect-oauth.js';
|
|
11
|
+
export { connectGitHubCredentials, } from './github-credentials.js';
|
|
12
|
+
export { connectLinearCredentials, } from './linear-credentials.js';
|
|
13
|
+
export { connectSlackCredentials, } from './slack-credentials.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LinearChannelCredentials } from 'eve/channels/linear';
|
|
2
|
+
import { type ConnectOptions, type ConnectTokenParams } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Token parameters accepted by {@link connectLinearCredentials}.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors {@link ConnectTokenParams} from `@vercel/connect`, minus
|
|
7
|
+
* `subject` — Linear Agent tokens are app-scoped, so `subject` is
|
|
8
|
+
* pinned to `{ type: "app" }` by this helper and cannot be overridden.
|
|
9
|
+
*/
|
|
10
|
+
export type ConnectLinearCredentialsParams = Omit<ConnectTokenParams, 'subject'>;
|
|
11
|
+
/**
|
|
12
|
+
* Build {@link LinearChannelCredentials} backed by a Vercel Connect
|
|
13
|
+
* connector that stores a Linear app access token.
|
|
14
|
+
*
|
|
15
|
+
* Eve uses `accessToken` for Linear GraphQL calls. The token is a
|
|
16
|
+
* function form so rotation, refresh, and multi-workspace tenancy stay
|
|
17
|
+
* delegated to Vercel Connect.
|
|
18
|
+
*
|
|
19
|
+
* The webhook verifier accepts Connect-forwarded webhooks authenticated
|
|
20
|
+
* with Vercel OIDC instead of Linear's webhook secret.
|
|
21
|
+
*/
|
|
22
|
+
export declare function connectLinearCredentials(connector: string, params?: ConnectLinearCredentialsParams, options?: ConnectOptions): LinearChannelCredentials;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { vercelOidc } from 'eve/channels/auth';
|
|
2
|
+
import { getToken, } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build {@link LinearChannelCredentials} backed by a Vercel Connect
|
|
5
|
+
* connector that stores a Linear app access token.
|
|
6
|
+
*
|
|
7
|
+
* Eve uses `accessToken` for Linear GraphQL calls. The token is a
|
|
8
|
+
* function form so rotation, refresh, and multi-workspace tenancy stay
|
|
9
|
+
* delegated to Vercel Connect.
|
|
10
|
+
*
|
|
11
|
+
* The webhook verifier accepts Connect-forwarded webhooks authenticated
|
|
12
|
+
* with Vercel OIDC instead of Linear's webhook secret.
|
|
13
|
+
*/
|
|
14
|
+
export function connectLinearCredentials(connector, params = {}, options) {
|
|
15
|
+
return {
|
|
16
|
+
accessToken: () => getToken(connector, { ...params, subject: { type: 'app' } }, options),
|
|
17
|
+
webhookVerifier: vercelOidc(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SlackChannelCredentials } from '
|
|
1
|
+
import type { SlackChannelCredentials } from 'eve/channels/slack';
|
|
2
2
|
import { type ConnectOptions, type ConnectTokenParams } from '../index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Token parameters accepted by {@link connectSlackCredentials}.
|
|
@@ -28,8 +28,8 @@ export type ConnectSlackCredentialsParams = Omit<ConnectTokenParams, 'subject'>;
|
|
|
28
28
|
* `installationId`, `scopes`, or `validityBufferMs`.
|
|
29
29
|
*
|
|
30
30
|
* ```ts
|
|
31
|
-
* import { slackRoute } from "
|
|
32
|
-
* import { connectSlackCredentials } from "@vercel/connect/
|
|
31
|
+
* import { slackRoute } from "eve/channels/slack";
|
|
32
|
+
* import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
33
33
|
*
|
|
34
34
|
* export default slackRoute({
|
|
35
35
|
* credentials: connectSlackCredentials("scl_..."),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getToken, } from '../index.js';
|
|
2
|
-
import { vercelOidc } from '
|
|
2
|
+
import { vercelOidc } from 'eve/channels/auth';
|
|
3
3
|
/**
|
|
4
4
|
* Build {@link SlackChannelCredentials} backed by a Vercel Connect
|
|
5
5
|
* connector that stores a Slack workspace's bot token.
|
|
@@ -19,8 +19,8 @@ import { vercelOidc } from 'experimental-ash/channels/auth';
|
|
|
19
19
|
* `installationId`, `scopes`, or `validityBufferMs`.
|
|
20
20
|
*
|
|
21
21
|
* ```ts
|
|
22
|
-
* import { slackRoute } from "
|
|
23
|
-
* import { connectSlackCredentials } from "@vercel/connect/
|
|
22
|
+
* import { slackRoute } from "eve/channels/slack";
|
|
23
|
+
* import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
24
24
|
*
|
|
25
25
|
* export default slackRoute({
|
|
26
26
|
* credentials: connectSlackCredentials("scl_..."),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { getToken, getTokenResponse, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
|
|
1
|
+
export { getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
|
|
2
2
|
export { startAuthorization, type ConnectAuthorizationOptions, type ConnectAuthorizationResponse, } from './authorization.js';
|
|
3
3
|
export type { ConnectAuthorizationDetail } from './authorization-details.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { getToken, getTokenResponse, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
|
|
1
|
+
export { getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
|
|
2
2
|
export { startAuthorization, } from './authorization.js';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { OAuthClientProvider } from '@ai-sdk/mcp';
|
|
2
|
+
import { type ConnectTokenParams } from '../token.js';
|
|
3
|
+
/** Options accepted by {@link connectAuthProvider}. */
|
|
4
|
+
export interface ConnectAuthProviderOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Override the Vercel OIDC token used to authenticate against the
|
|
7
|
+
* Connect API. Defaults to the `@vercel/oidc` resolver used by the
|
|
8
|
+
* rest of `@vercel/connect`. Useful for tests and non-Vercel
|
|
9
|
+
* runtimes.
|
|
10
|
+
*
|
|
11
|
+
* Accepts a callback because the adapter is long-lived: `tokens()`
|
|
12
|
+
* is invoked across many turns and refreshes, so a captured string
|
|
13
|
+
* would go stale. When a function is provided it is resolved at
|
|
14
|
+
* call time.
|
|
15
|
+
*/
|
|
16
|
+
readonly vercelToken?: string | (() => string | Promise<string>);
|
|
17
|
+
/**
|
|
18
|
+
* Where to send the user after they finish granting access on
|
|
19
|
+
* Connect's hosted consent page. Forwarded to Connect's
|
|
20
|
+
* `startAuthorization` (as its `callbackUrl`) and also surfaced
|
|
21
|
+
* through the MCP-spec `OAuthClientProvider.redirectUrl` getter.
|
|
22
|
+
*
|
|
23
|
+
* Defaults to an empty string — Connect then falls back to the
|
|
24
|
+
* connector's server-side registered redirect.
|
|
25
|
+
*/
|
|
26
|
+
readonly redirectUrl?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Request the device-authorization flow when minting a consent
|
|
29
|
+
* challenge. When `true`, Connect returns a `deviceCode` (and
|
|
30
|
+
* `expiresAt`) on the {@link ConsentChallenge} for out-of-band /
|
|
31
|
+
* headless approval. Defaults to `false`.
|
|
32
|
+
*/
|
|
33
|
+
readonly deviceCode?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Called when Vercel Connect reports that the user has not yet
|
|
36
|
+
* authorized the connector. The caller decides how to surface the
|
|
37
|
+
* consent URL — `redirect()`, throw a typed error, emit a custom
|
|
38
|
+
* UI chunk, etc. If omitted, `connectAuthProvider` throws
|
|
39
|
+
* {@link ConsentRequiredError}.
|
|
40
|
+
*/
|
|
41
|
+
readonly onConsentRequired?: (challenge: ConsentChallenge) => void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returned to {@link ConnectAuthProviderOptions.onConsentRequired} (or
|
|
45
|
+
* raised inside {@link ConsentRequiredError}) when Connect has no
|
|
46
|
+
* cached grant for the configured subject and the caller must surface
|
|
47
|
+
* the consent URL.
|
|
48
|
+
*/
|
|
49
|
+
export interface ConsentChallenge {
|
|
50
|
+
readonly connector: string;
|
|
51
|
+
readonly subject: ConnectTokenParams['subject'];
|
|
52
|
+
/** Consent URL to redirect the user to. */
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly request: string;
|
|
55
|
+
readonly verifier: string;
|
|
56
|
+
/**
|
|
57
|
+
* Device code to display to the user when Connect issues a
|
|
58
|
+
* device-flow authorization. Present only when the connector uses
|
|
59
|
+
* device flow.
|
|
60
|
+
*/
|
|
61
|
+
readonly deviceCode?: string;
|
|
62
|
+
/** Epoch-ms expiry of the consent challenge, when Connect reports one. */
|
|
63
|
+
readonly expiresAt?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Thrown by the {@link connectAuthProvider} default
|
|
67
|
+
* `onConsentRequired` handler when the user has no Connect grant for
|
|
68
|
+
* the configured subject. Catch at the boundary
|
|
69
|
+
* (`route handler` / `streamText` call site) and redirect to
|
|
70
|
+
* `error.url`.
|
|
71
|
+
*/
|
|
72
|
+
export declare class ConsentRequiredError extends Error {
|
|
73
|
+
readonly name = "ConsentRequiredError";
|
|
74
|
+
readonly connector: string;
|
|
75
|
+
readonly subject: ConnectTokenParams['subject'];
|
|
76
|
+
readonly url: string;
|
|
77
|
+
readonly request: string;
|
|
78
|
+
readonly verifier: string;
|
|
79
|
+
readonly deviceCode?: string;
|
|
80
|
+
readonly expiresAt?: number;
|
|
81
|
+
constructor(challenge: ConsentChallenge);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds an MCP-spec {@link OAuthClientProvider} backed by Vercel
|
|
85
|
+
* Connect. The returned object delegates `tokens()` to
|
|
86
|
+
* {@link getTokenResponse} and `redirectToAuthorization` to
|
|
87
|
+
* {@link startAuthorization}. Connect owns client registration, PKCE,
|
|
88
|
+
* state, and the callback handshake server-side, so most of the
|
|
89
|
+
* `OAuthClientProvider` surface is intentionally a no-op.
|
|
90
|
+
*
|
|
91
|
+
* @param connector Vercel Connect connector UID (e.g. `oauth/linear`)
|
|
92
|
+
* or opaque connector id.
|
|
93
|
+
* @param params Token request parameters — always specify a
|
|
94
|
+
* `subject`; the three subject types
|
|
95
|
+
* (`'app'` / `'user'` / `'jwt-bearer'`) have distinct security
|
|
96
|
+
* semantics.
|
|
97
|
+
* @param options Optional adapter behavior overrides.
|
|
98
|
+
*/
|
|
99
|
+
export declare function connectAuthProvider(connector: string, params: ConnectTokenParams, options?: ConnectAuthProviderOptions): OAuthClientProvider;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { startAuthorization } from '../authorization.js';
|
|
2
|
+
import { ConnectorInstallationRequiredError, UserAuthorizationRequiredError, getTokenResponse, } from '../token.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by the {@link connectAuthProvider} default
|
|
5
|
+
* `onConsentRequired` handler when the user has no Connect grant for
|
|
6
|
+
* the configured subject. Catch at the boundary
|
|
7
|
+
* (`route handler` / `streamText` call site) and redirect to
|
|
8
|
+
* `error.url`.
|
|
9
|
+
*/
|
|
10
|
+
export class ConsentRequiredError extends Error {
|
|
11
|
+
name = 'ConsentRequiredError';
|
|
12
|
+
connector;
|
|
13
|
+
subject;
|
|
14
|
+
url;
|
|
15
|
+
request;
|
|
16
|
+
verifier;
|
|
17
|
+
deviceCode;
|
|
18
|
+
expiresAt;
|
|
19
|
+
constructor(challenge) {
|
|
20
|
+
super(`Vercel Connect: user authorization required for connector "${challenge.connector}". Redirect the user to challenge.url to grant access.`);
|
|
21
|
+
this.connector = challenge.connector;
|
|
22
|
+
this.subject = challenge.subject;
|
|
23
|
+
this.url = challenge.url;
|
|
24
|
+
this.request = challenge.request;
|
|
25
|
+
this.verifier = challenge.verifier;
|
|
26
|
+
this.deviceCode = challenge.deviceCode;
|
|
27
|
+
this.expiresAt = challenge.expiresAt;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const EMPTY_CLIENT_METADATA = {
|
|
31
|
+
redirect_uris: [],
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Builds an MCP-spec {@link OAuthClientProvider} backed by Vercel
|
|
35
|
+
* Connect. The returned object delegates `tokens()` to
|
|
36
|
+
* {@link getTokenResponse} and `redirectToAuthorization` to
|
|
37
|
+
* {@link startAuthorization}. Connect owns client registration, PKCE,
|
|
38
|
+
* state, and the callback handshake server-side, so most of the
|
|
39
|
+
* `OAuthClientProvider` surface is intentionally a no-op.
|
|
40
|
+
*
|
|
41
|
+
* @param connector Vercel Connect connector UID (e.g. `oauth/linear`)
|
|
42
|
+
* or opaque connector id.
|
|
43
|
+
* @param params Token request parameters — always specify a
|
|
44
|
+
* `subject`; the three subject types
|
|
45
|
+
* (`'app'` / `'user'` / `'jwt-bearer'`) have distinct security
|
|
46
|
+
* semantics.
|
|
47
|
+
* @param options Optional adapter behavior overrides.
|
|
48
|
+
*/
|
|
49
|
+
export function connectAuthProvider(connector, params, options) {
|
|
50
|
+
const redirectUrl = options?.redirectUrl ?? '';
|
|
51
|
+
async function resolveVercelToken() {
|
|
52
|
+
const { vercelToken } = options ?? {};
|
|
53
|
+
if (typeof vercelToken === 'function') {
|
|
54
|
+
return vercelToken();
|
|
55
|
+
}
|
|
56
|
+
return vercelToken;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
async tokens() {
|
|
60
|
+
try {
|
|
61
|
+
const vercelToken = await resolveVercelToken();
|
|
62
|
+
const response = await getTokenResponse(connector, params, vercelToken !== undefined ? { vercelToken } : undefined);
|
|
63
|
+
const expiresInSeconds = Math.max(1, Math.floor((response.expiresAt - Date.now()) / 1000));
|
|
64
|
+
return {
|
|
65
|
+
access_token: response.token,
|
|
66
|
+
token_type: 'Bearer',
|
|
67
|
+
expires_in: expiresInSeconds,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err instanceof UserAuthorizationRequiredError) {
|
|
72
|
+
// Returning undefined lets the MCP transport's auth()
|
|
73
|
+
// orchestrator trigger redirectToAuthorization below,
|
|
74
|
+
// where we surface the Connect consent URL.
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (err instanceof ConnectorInstallationRequiredError) {
|
|
78
|
+
// Configuration problem, not a per-user issue. Surface
|
|
79
|
+
// immediately rather than masking as a consent challenge.
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async redirectToAuthorization(_authorizationUrl) {
|
|
86
|
+
// The URL argument is the MCP server's discovered authorization
|
|
87
|
+
// endpoint. We ignore it — Connect has its own consent URL
|
|
88
|
+
// backed by the connector's registered OAuth client.
|
|
89
|
+
const vercelToken = await resolveVercelToken();
|
|
90
|
+
const response = await startAuthorization(connector, params, {
|
|
91
|
+
...(vercelToken !== undefined && { vercelToken }),
|
|
92
|
+
// Forward the post-consent return URL so the user lands back
|
|
93
|
+
// in the app. Falsy (the default empty string) => Connect uses
|
|
94
|
+
// the connector's registered redirect.
|
|
95
|
+
...(redirectUrl && { callbackUrl: redirectUrl }),
|
|
96
|
+
...(options?.deviceCode && { deviceCode: true }),
|
|
97
|
+
});
|
|
98
|
+
const challenge = {
|
|
99
|
+
connector,
|
|
100
|
+
subject: params.subject,
|
|
101
|
+
url: response.url,
|
|
102
|
+
request: response.request,
|
|
103
|
+
verifier: response.verifier,
|
|
104
|
+
deviceCode: response.deviceCode,
|
|
105
|
+
expiresAt: response.expiresAt,
|
|
106
|
+
};
|
|
107
|
+
if (options?.onConsentRequired) {
|
|
108
|
+
await options.onConsentRequired(challenge);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
throw new ConsentRequiredError(challenge);
|
|
112
|
+
},
|
|
113
|
+
saveTokens(_tokens) {
|
|
114
|
+
// No-op: Connect owns token persistence server-side. The
|
|
115
|
+
// in-memory LRU cache in @vercel/connect's token module
|
|
116
|
+
// handles per-request reuse.
|
|
117
|
+
},
|
|
118
|
+
saveCodeVerifier(_codeVerifier) {
|
|
119
|
+
// No-op: Connect owns PKCE.
|
|
120
|
+
},
|
|
121
|
+
codeVerifier() {
|
|
122
|
+
// The MCP transport never reaches this when tokens() /
|
|
123
|
+
// redirectToAuthorization are wired correctly. If a future
|
|
124
|
+
// transport change starts calling it, throw loudly rather
|
|
125
|
+
// than fabricate a bogus verifier.
|
|
126
|
+
throw new Error('@vercel/connect: OAuthClientProvider.codeVerifier() is unsupported. Vercel Connect owns the PKCE flow server-side; this method should never be invoked by a correctly-configured MCP transport.');
|
|
127
|
+
},
|
|
128
|
+
get redirectUrl() {
|
|
129
|
+
// Same value forwarded to Connect as the post-consent return
|
|
130
|
+
// URL. Surfaced here to satisfy the OAuthClientProvider
|
|
131
|
+
// contract; the standard flow that reads it is never triggered.
|
|
132
|
+
return redirectUrl;
|
|
133
|
+
},
|
|
134
|
+
get clientMetadata() {
|
|
135
|
+
// Returned only during dynamic client registration, which we
|
|
136
|
+
// never trigger. A minimal but valid value satisfies the
|
|
137
|
+
// contract.
|
|
138
|
+
return EMPTY_CLIENT_METADATA;
|
|
139
|
+
},
|
|
140
|
+
clientInformation() {
|
|
141
|
+
// The connector UID stands in as the logical client id. The
|
|
142
|
+
// MCP transport uses this for cache keys and debug output;
|
|
143
|
+
// tying it to the Connect identifier is more useful than
|
|
144
|
+
// returning a synthetic value.
|
|
145
|
+
return { client_id: connector };
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/mcp` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Holds the MCP-spec `OAuthClientProvider` adapter for Vercel
|
|
5
|
+
* Connect. Plug the returned object into any MCP client that
|
|
6
|
+
* consumes the spec — `@ai-sdk/mcp`, the official
|
|
7
|
+
* `@modelcontextprotocol/typescript-sdk`, Mastra's MCP client,
|
|
8
|
+
* etc. AI SDK consumers should import the same function from
|
|
9
|
+
* `@vercel/connect/ai-sdk` instead; that subpath re-exports from
|
|
10
|
+
* here.
|
|
11
|
+
*
|
|
12
|
+
* `@ai-sdk/mcp` is an optional peer dependency: importing this
|
|
13
|
+
* entrypoint requires it to be installed in the consumer project,
|
|
14
|
+
* but the rest of `@vercel/connect` works without it.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createMCPClient } from '@ai-sdk/mcp';
|
|
18
|
+
* import { connectAuthProvider } from '@vercel/connect/mcp';
|
|
19
|
+
*
|
|
20
|
+
* const mcpClient = await createMCPClient({
|
|
21
|
+
* transport: {
|
|
22
|
+
* type: 'http',
|
|
23
|
+
* url: 'https://mcp.linear.app',
|
|
24
|
+
* authProvider: connectAuthProvider('oauth/linear', {
|
|
25
|
+
* subject: { type: 'user', id: userId },
|
|
26
|
+
* scopes: ['read'],
|
|
27
|
+
* }),
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export { connectAuthProvider, ConsentRequiredError, type ConnectAuthProviderOptions, type ConsentChallenge, } from './connect-auth-provider.js';
|
|
33
|
+
export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, type ConnectErrorOptions, type ConnectTokenParams, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from '../token.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `@vercel/connect/mcp` subpath.
|
|
3
|
+
*
|
|
4
|
+
* Holds the MCP-spec `OAuthClientProvider` adapter for Vercel
|
|
5
|
+
* Connect. Plug the returned object into any MCP client that
|
|
6
|
+
* consumes the spec — `@ai-sdk/mcp`, the official
|
|
7
|
+
* `@modelcontextprotocol/typescript-sdk`, Mastra's MCP client,
|
|
8
|
+
* etc. AI SDK consumers should import the same function from
|
|
9
|
+
* `@vercel/connect/ai-sdk` instead; that subpath re-exports from
|
|
10
|
+
* here.
|
|
11
|
+
*
|
|
12
|
+
* `@ai-sdk/mcp` is an optional peer dependency: importing this
|
|
13
|
+
* entrypoint requires it to be installed in the consumer project,
|
|
14
|
+
* but the rest of `@vercel/connect` works without it.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createMCPClient } from '@ai-sdk/mcp';
|
|
18
|
+
* import { connectAuthProvider } from '@vercel/connect/mcp';
|
|
19
|
+
*
|
|
20
|
+
* const mcpClient = await createMCPClient({
|
|
21
|
+
* transport: {
|
|
22
|
+
* type: 'http',
|
|
23
|
+
* url: 'https://mcp.linear.app',
|
|
24
|
+
* authProvider: connectAuthProvider('oauth/linear', {
|
|
25
|
+
* subject: { type: 'user', id: userId },
|
|
26
|
+
* scopes: ['read'],
|
|
27
|
+
* }),
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export { connectAuthProvider, ConsentRequiredError, } from './connect-auth-provider.js';
|
|
33
|
+
export { ConnectError, ConnectorInstallationRequiredError, NoValidTokenError, UserAuthorizationRequiredError, } from '../token.js';
|
package/dist/token.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface ConnectTokenParams {
|
|
|
22
22
|
subject: ConnectTokenSubject;
|
|
23
23
|
installationId?: string;
|
|
24
24
|
audience?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Access scopes to request. Use `['*']` to request the default scopes for
|
|
27
|
+
* the specified subject type.
|
|
28
|
+
*/
|
|
25
29
|
scopes?: string[];
|
|
26
30
|
resources?: string[];
|
|
27
31
|
authorizationDetails?: ConnectAuthorizationDetail[];
|
|
@@ -79,4 +83,8 @@ export interface ConnectOptions {
|
|
|
79
83
|
}
|
|
80
84
|
export declare function getToken(connector: string, params: ConnectTokenParams, options?: ConnectOptions): Promise<string>;
|
|
81
85
|
export declare function getTokenResponse(connector: string, params: ConnectTokenParams, options?: ConnectOptions): Promise<ConnectTokenResponse>;
|
|
86
|
+
export declare function revokeToken(connector: string, params: {
|
|
87
|
+
subject: ConnectTokenSubject;
|
|
88
|
+
installationId?: string;
|
|
89
|
+
}, options?: ConnectOptions): Promise<void>;
|
|
82
90
|
export declare function createConnectErrorFromResponse(response: Response, fallbackMessage: string): Promise<ConnectError>;
|
package/dist/token.js
CHANGED
|
@@ -68,6 +68,23 @@ export async function getTokenResponse(connector, params, options) {
|
|
|
68
68
|
cache.set(cacheKey, { response: data, lastUsed: Date.now() });
|
|
69
69
|
return data;
|
|
70
70
|
}
|
|
71
|
+
export async function revokeToken(connector, params, options) {
|
|
72
|
+
const vercelToken = options?.vercelToken ?? (await getVercelOidcToken());
|
|
73
|
+
const endpoint = `https://api.vercel.com/v1/connect/connectors/${encodeURIComponent(connector)}/tokens`;
|
|
74
|
+
const response = await fetch(endpoint, {
|
|
75
|
+
method: 'DELETE',
|
|
76
|
+
headers: {
|
|
77
|
+
Accept: 'application/json',
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
Authorization: `Bearer ${vercelToken}`,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(params),
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw await createConnectErrorFromResponse(response, 'Failed to revoke token');
|
|
85
|
+
}
|
|
86
|
+
cache.clear();
|
|
87
|
+
}
|
|
71
88
|
const DEFAULT_VALIDITY_BUFFER_MS = 30_000;
|
|
72
89
|
const MAX_CACHE_SIZE = 100;
|
|
73
90
|
const cache = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/connect",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./
|
|
17
|
-
"types": "./dist/
|
|
18
|
-
"default": "./dist/
|
|
16
|
+
"./eve": {
|
|
17
|
+
"types": "./dist/eve/index.d.ts",
|
|
18
|
+
"default": "./dist/eve/index.js"
|
|
19
19
|
},
|
|
20
20
|
"./betterauth": {
|
|
21
21
|
"types": "./dist/betterauth/index.d.ts",
|
|
@@ -24,34 +24,52 @@
|
|
|
24
24
|
"./authjs": {
|
|
25
25
|
"types": "./dist/authjs/index.d.ts",
|
|
26
26
|
"default": "./dist/authjs/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./mcp": {
|
|
29
|
+
"types": "./dist/mcp/index.d.ts",
|
|
30
|
+
"default": "./dist/mcp/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./ai-sdk": {
|
|
33
|
+
"types": "./dist/ai-sdk/index.d.ts",
|
|
34
|
+
"default": "./dist/ai-sdk/index.js"
|
|
27
35
|
}
|
|
28
36
|
},
|
|
29
37
|
"files": [
|
|
30
38
|
"dist"
|
|
31
39
|
],
|
|
32
40
|
"dependencies": {
|
|
33
|
-
"@vercel/oidc": "3.6.
|
|
41
|
+
"@vercel/oidc": "3.6.1"
|
|
34
42
|
},
|
|
35
43
|
"peerDependencies": {
|
|
44
|
+
"@ai-sdk/mcp": "^1 || ^2",
|
|
36
45
|
"@auth/core": ">=0.37.0",
|
|
46
|
+
"ai": "^6 || ^7",
|
|
37
47
|
"better-auth": ">=1.5.0",
|
|
38
|
-
"
|
|
48
|
+
"eve": ">=0.6.0-beta.1"
|
|
39
49
|
},
|
|
40
50
|
"peerDependenciesMeta": {
|
|
51
|
+
"@ai-sdk/mcp": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
41
54
|
"@auth/core": {
|
|
42
55
|
"optional": true
|
|
43
56
|
},
|
|
57
|
+
"ai": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
44
60
|
"better-auth": {
|
|
45
61
|
"optional": true
|
|
46
62
|
},
|
|
47
|
-
"
|
|
63
|
+
"eve": {
|
|
48
64
|
"optional": true
|
|
49
65
|
}
|
|
50
66
|
},
|
|
51
67
|
"devDependencies": {
|
|
68
|
+
"@ai-sdk/mcp": "2.0.0-beta.37",
|
|
52
69
|
"@auth/core": "0.37.4",
|
|
70
|
+
"ai": "7.0.0-beta.116",
|
|
53
71
|
"better-auth": "1.5.5",
|
|
54
|
-
"
|
|
72
|
+
"eve": "0.6.0-beta.1",
|
|
55
73
|
"typescript": "^5"
|
|
56
74
|
},
|
|
57
75
|
"private": false,
|
|
@@ -60,6 +78,9 @@
|
|
|
60
78
|
},
|
|
61
79
|
"scripts": {
|
|
62
80
|
"build": "tsc",
|
|
63
|
-
"typecheck": "tsc --noEmit"
|
|
81
|
+
"typecheck": "tsc --noEmit",
|
|
82
|
+
"type-check": "tsc --noEmit",
|
|
83
|
+
"test": "vitest run --config ../../vitest.config.mts",
|
|
84
|
+
"vitest-unit": "vitest run --config ../../vitest.config.mts"
|
|
64
85
|
}
|
|
65
86
|
}
|
package/dist/ash/index.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public surface of the `@vercel/connect/ash` subpath.
|
|
3
|
-
*
|
|
4
|
-
* Holds Ash-specific helpers that adapt the Vercel Connect SDK to
|
|
5
|
-
* Ash's connection runtime. Each helper lives in its own module;
|
|
6
|
-
* this barrel re-exports the public API so consumers import
|
|
7
|
-
* everything from `@vercel/connect/ash`.
|
|
8
|
-
*/
|
|
9
|
-
export { connect, type AshAuthorizationInput, type AshAuthorizationOptions, type AshConnectAuthorizationDefinition, type ConnectAuthorizationPhase, type ConnectAuthorizationState, type VercelConnectMetadata, } from './connection-authorization.js';
|
|
10
|
-
export { CONNECT_OAUTH_ISSUER, connectOAuth, type ConnectOAuthAudienceEnvironment, type ConnectOAuthEnvironment, type ConnectOAuthOptions, } from './connect-oauth.js';
|
|
11
|
-
export { connectSlackCredentials } from './slack-credentials.js';
|
package/dist/ash/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public surface of the `@vercel/connect/ash` subpath.
|
|
3
|
-
*
|
|
4
|
-
* Holds Ash-specific helpers that adapt the Vercel Connect SDK to
|
|
5
|
-
* Ash's connection runtime. Each helper lives in its own module;
|
|
6
|
-
* this barrel re-exports the public API so consumers import
|
|
7
|
-
* everything from `@vercel/connect/ash`.
|
|
8
|
-
*/
|
|
9
|
-
export { connect, } from './connection-authorization.js';
|
|
10
|
-
export { CONNECT_OAUTH_ISSUER, connectOAuth, } from './connect-oauth.js';
|
|
11
|
-
export { connectSlackCredentials } from './slack-credentials.js';
|