@better-auth/mcp 1.7.22 → 1.7.23
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/dist/index.d.mts +18 -6
- package/dist/index.mjs +24 -5
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OAuthOptions, Scope, oauthProvider } from "@better-auth/oauth-provider";
|
|
2
2
|
import { DpopReplayReservations, DpopReplayStore } from "better-auth/oauth2";
|
|
3
|
-
import { Awaitable } from "@better-auth/core";
|
|
3
|
+
import { AuthContext, Awaitable } from "@better-auth/core";
|
|
4
4
|
import { JWTPayload, JWTVerifyOptions } from "jose";
|
|
5
5
|
import { BetterAuthOptions } from "better-auth/types";
|
|
6
6
|
interface McpProtectedRequestHandlerOptions {
|
|
@@ -36,9 +36,21 @@ interface McpProtectedRequestHandlerOptions {
|
|
|
36
36
|
dpop?: {
|
|
37
37
|
proofMaxAgeSeconds?: number;
|
|
38
38
|
signingAlgorithms?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Store that rejects a DPoP proof that was already used.
|
|
41
|
+
*
|
|
42
|
+
* Unlike `requireMcpAuth`, this handler has no auth instance to store
|
|
43
|
+
* proofs in, so it defaults to a process-local in-memory store. That is
|
|
44
|
+
* safe only for a single long-lived instance. For multiple instances or
|
|
45
|
+
* serverless, pass a shared store, such as
|
|
46
|
+
* `createDpopReplayStore((await auth.$context).internalAdapter)` from
|
|
47
|
+
* `better-auth/oauth2`. With secondary storage, that store also needs
|
|
48
|
+
* `verification.storeInDatabase: true`, or every DPoP request fails.
|
|
49
|
+
*/
|
|
39
50
|
replayStore?: DpopReplayStore;
|
|
40
51
|
};
|
|
41
52
|
}
|
|
53
|
+
type McpRequestHandler = (request: Request, accessTokenClaims: JWTPayload) => Awaitable<Response>;
|
|
42
54
|
/**
|
|
43
55
|
* A request middleware handler that verifies an MCP access token and responds
|
|
44
56
|
* with an RFC 9728 `WWW-Authenticate` header for unauthenticated requests.
|
|
@@ -47,7 +59,7 @@ interface McpProtectedRequestHandlerOptions {
|
|
|
47
59
|
*
|
|
48
60
|
* @external
|
|
49
61
|
*/
|
|
50
|
-
export declare const createMcpProtectedRequestHandler: (options: McpProtectedRequestHandlerOptions, handler:
|
|
62
|
+
export declare const createMcpProtectedRequestHandler: (options: McpProtectedRequestHandlerOptions, handler: McpRequestHandler) => (request: Request) => Promise<Response>;
|
|
51
63
|
/**
|
|
52
64
|
* Options for the {@link mcp} plugin: the full OAuth provider configuration plus
|
|
53
65
|
* the MCP resource identifier.
|
|
@@ -134,8 +146,9 @@ interface RequireMcpAuthOptions {
|
|
|
134
146
|
*/
|
|
135
147
|
issuer?: string;
|
|
136
148
|
/**
|
|
137
|
-
* URL of the authorization server's JWKS.
|
|
138
|
-
*
|
|
149
|
+
* URL of the authorization server's JWKS. When unset, the signing keys are
|
|
150
|
+
* read from the auth instance in-process, without an HTTP request, or
|
|
151
|
+
* fetched from the JWT plugin's `jwks.remoteUrl` when it sets one.
|
|
139
152
|
*/
|
|
140
153
|
jwksUrl?: string;
|
|
141
154
|
/**
|
|
@@ -184,8 +197,7 @@ interface RequireMcpAuthOptions {
|
|
|
184
197
|
*/
|
|
185
198
|
export declare const requireMcpAuth: <Auth extends {
|
|
186
199
|
options: BetterAuthOptions;
|
|
187
|
-
$context: Promise<{
|
|
188
|
-
baseURL: string;
|
|
200
|
+
$context: Promise<Pick<AuthContext, "baseURL" | "getPlugin"> & {
|
|
189
201
|
internalAdapter: DpopReplayReservations;
|
|
190
202
|
}>;
|
|
191
203
|
}>(auth: Auth, handler: (request: Request, accessTokenClaims: JWTPayload) => Awaitable<Response>, opts?: RequireMcpAuthOptions) => (req: Request) => Promise<Response>;
|
package/dist/index.mjs
CHANGED
|
@@ -46,7 +46,14 @@ function toChallengeResponse(error, resource, opts) {
|
|
|
46
46
|
*
|
|
47
47
|
* @external
|
|
48
48
|
*/
|
|
49
|
-
const createMcpProtectedRequestHandler = (options, handler) =>
|
|
49
|
+
const createMcpProtectedRequestHandler = (options, handler) => createProtectedRequestHandler(options, handler);
|
|
50
|
+
/**
|
|
51
|
+
* {@link createMcpProtectedRequestHandler} that can also read the
|
|
52
|
+
* authorization server's keys in-process.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
const createProtectedRequestHandler = (options, handler) => {
|
|
50
57
|
const resource = validateMcpResource(options.audience);
|
|
51
58
|
const resolvedChallengeOptions = {
|
|
52
59
|
challengeScopes: options.challengeScopes ?? options.requiredScopes,
|
|
@@ -59,6 +66,8 @@ const createMcpProtectedRequestHandler = (options, handler) => {
|
|
|
59
66
|
audience: options.audience
|
|
60
67
|
},
|
|
61
68
|
jwksUrl: options.jwksUrl,
|
|
69
|
+
jwksFetch: options.jwksFetch,
|
|
70
|
+
jwksCacheKey: options.jwksCacheKey,
|
|
62
71
|
remoteVerify: options.remoteVerify,
|
|
63
72
|
requiredScopes: options.requiredScopes,
|
|
64
73
|
isScopeSatisfied: options.isScopeSatisfied,
|
|
@@ -77,7 +86,10 @@ const createMcpProtectedRequestHandler = (options, handler) => {
|
|
|
77
86
|
try {
|
|
78
87
|
return await handler(request, accessTokenClaims);
|
|
79
88
|
} catch (error) {
|
|
80
|
-
if (isInsufficientScopeError(error)) return toChallengeResponse(error, resource,
|
|
89
|
+
if (isInsufficientScopeError(error)) return toChallengeResponse(error, resource, {
|
|
90
|
+
...resolvedChallengeOptions,
|
|
91
|
+
request
|
|
92
|
+
});
|
|
81
93
|
throw error;
|
|
82
94
|
}
|
|
83
95
|
};
|
|
@@ -223,12 +235,19 @@ const mcp = (options) => {
|
|
|
223
235
|
const requireMcpAuth = (auth, handler, opts) => {
|
|
224
236
|
if (opts?.resource !== void 0) validateMcpResource(opts.resource);
|
|
225
237
|
return async (req) => {
|
|
226
|
-
const
|
|
238
|
+
const authContext = await auth.$context;
|
|
239
|
+
const { baseURL, internalAdapter } = authContext;
|
|
227
240
|
if (!baseURL) throw new Error("requireMcpAuth requires a resolvable base URL. For dynamic base URLs use `createMcpProtectedRequestHandler` with explicit verification options.");
|
|
228
241
|
const issuer = opts?.issuer ?? baseURL;
|
|
229
242
|
const resource = opts?.resource ?? baseURL;
|
|
230
|
-
const
|
|
231
|
-
|
|
243
|
+
const jwtPlugin = authContext.getPlugin("jwt");
|
|
244
|
+
const localJwks = !opts?.jwksUrl && jwtPlugin && !jwtPlugin.options?.jwks?.remoteUrl ? {
|
|
245
|
+
jwksFetch: async () => jwtPlugin.endpoints.getJwks({ context: authContext }),
|
|
246
|
+
jwksCacheKey: authContext
|
|
247
|
+
} : void 0;
|
|
248
|
+
const jwksUrl = opts?.jwksUrl ?? jwtPlugin?.options?.jwks?.remoteUrl ?? `${baseURL}/jwks`;
|
|
249
|
+
return createProtectedRequestHandler({
|
|
250
|
+
...localJwks,
|
|
232
251
|
issuer,
|
|
233
252
|
audience: resource,
|
|
234
253
|
jwtVerifyOptions: { requiredClaims: ["azp"] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/mcp",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.23",
|
|
4
4
|
"description": "Model Context Protocol (MCP) plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,20 +46,20 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@better-auth/oauth-provider": "^1.7.
|
|
49
|
+
"@better-auth/oauth-provider": "^1.7.23",
|
|
50
50
|
"jose": "^6.1.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@better-auth/core": "1.7.
|
|
53
|
+
"@better-auth/core": "1.7.23",
|
|
54
54
|
"@modelcontextprotocol/client": "2.3.1",
|
|
55
55
|
"@modelcontextprotocol/server": "2.3.1",
|
|
56
|
-
"better-auth": "1.7.
|
|
56
|
+
"better-auth": "1.7.23",
|
|
57
57
|
"better-call": "1.4.5",
|
|
58
58
|
"tsdown": "0.23.1"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@better-auth/core": "^1.7.
|
|
62
|
-
"better-auth": "^1.7.
|
|
61
|
+
"@better-auth/core": "^1.7.23",
|
|
62
|
+
"better-auth": "^1.7.23",
|
|
63
63
|
"better-call": "1.4.5"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|