@better-auth/mcp 1.7.0-rc.1 → 1.7.0-rc.2
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/client/adapters.d.mts +0 -1
- package/dist/client/adapters.mjs +7 -3
- package/dist/client/index.d.mts +2 -1
- package/dist/client/index.mjs +32 -9
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/package.json +7 -7
package/dist/client/adapters.mjs
CHANGED
|
@@ -5,7 +5,8 @@ const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource"
|
|
|
5
5
|
function getProtectedResourceMetadataPath(resource) {
|
|
6
6
|
const resourceUrl = new URL(resource);
|
|
7
7
|
if (resourceUrl.origin === "null") return PROTECTED_RESOURCE_METADATA_PATH;
|
|
8
|
-
|
|
8
|
+
const resourcePath = resourceUrl.pathname === "/" ? "" : resourceUrl.pathname.replace(/\/$/, "");
|
|
9
|
+
return `${PROTECTED_RESOURCE_METADATA_PATH}${resourcePath}`;
|
|
9
10
|
}
|
|
10
11
|
function getProtectedResourceMetadataURL(resource) {
|
|
11
12
|
const resourceUrl = new URL(resource);
|
|
@@ -36,14 +37,17 @@ function mcpAuthHono(options) {
|
|
|
36
37
|
if (isDpopBindingError(error)) return unauthorized(c, dpopChallenge, "Invalid or expired token");
|
|
37
38
|
throw error;
|
|
38
39
|
}
|
|
39
|
-
if (!session)
|
|
40
|
+
if (!session) {
|
|
41
|
+
const challenge = parseAccessTokenAuthorization(authHeader)?.scheme === "DPoP" || !!c.req.header("DPoP") ? dpopChallenge : `Bearer resource_metadata="${resourceMetadata}"`;
|
|
42
|
+
return unauthorized(c, challenge, authHeader ? "Invalid or expired token" : "Unauthorized: Authentication required");
|
|
43
|
+
}
|
|
40
44
|
c.set("mcpSession", session);
|
|
41
45
|
await next();
|
|
42
46
|
};
|
|
43
47
|
const discoveryRoutes = (app, serverURL) => {
|
|
44
48
|
const discoveryFn = client.discoveryHandler();
|
|
45
49
|
const protectedResourceFn = client.protectedResourceHandler(serverURL);
|
|
46
|
-
const protectedResourcePaths = new Set([PROTECTED_RESOURCE_METADATA_PATH, getProtectedResourceMetadataPath(options.resource ?? client.authURL)]);
|
|
50
|
+
const protectedResourcePaths = /* @__PURE__ */ new Set([PROTECTED_RESOURCE_METADATA_PATH, getProtectedResourceMetadataPath(options.resource ?? client.authURL)]);
|
|
47
51
|
app.get("/.well-known/oauth-authorization-server", async (c) => {
|
|
48
52
|
const response = await discoveryFn(c.req.raw);
|
|
49
53
|
const data = await response.json().catch(() => ({ error: "Invalid response from auth server" }));
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
|
|
2
2
|
import { JWTPayload } from "jose";
|
|
3
|
-
|
|
4
3
|
//#region src/client/index.d.ts
|
|
5
4
|
interface McpResourceClientOptions {
|
|
6
5
|
authURL: string;
|
|
@@ -30,7 +29,9 @@ interface NodeLikeRequest {
|
|
|
30
29
|
}
|
|
31
30
|
interface NodeLikeResponse {
|
|
32
31
|
set?: (name: string, value: string) => void;
|
|
32
|
+
get?: (name: string) => string | undefined;
|
|
33
33
|
setHeader?: (name: string, value: string) => void;
|
|
34
|
+
getHeader?: (name: string) => string | number | string[] | undefined;
|
|
34
35
|
status?: (code: number) => {
|
|
35
36
|
json: (body: unknown) => void;
|
|
36
37
|
};
|
package/dist/client/index.mjs
CHANGED
|
@@ -30,7 +30,19 @@ function makeWWWAuthenticate(authURL, resource) {
|
|
|
30
30
|
function makeDpopWWWAuthenticate(algorithms) {
|
|
31
31
|
return `DPoP algs="${algorithms.map((alg) => alg.replace(/[\r\n"\\]/g, "")).join(" ")}"`;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function addExposeHeader(headers, headerName) {
|
|
34
|
+
const exposedHeaders = headers["Access-Control-Expose-Headers"];
|
|
35
|
+
if (!exposedHeaders) return {
|
|
36
|
+
...headers,
|
|
37
|
+
"Access-Control-Expose-Headers": headerName
|
|
38
|
+
};
|
|
39
|
+
const alreadyExposed = exposedHeaders.split(",").some((header) => header.trim().toLowerCase() === headerName.toLowerCase());
|
|
40
|
+
return {
|
|
41
|
+
...headers,
|
|
42
|
+
"Access-Control-Expose-Headers": alreadyExposed ? exposedHeaders : `${exposedHeaders}, ${headerName}`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function make401Response(wwwAuth, corsHeaders) {
|
|
34
46
|
return Response.json({
|
|
35
47
|
jsonrpc: "2.0",
|
|
36
48
|
error: {
|
|
@@ -41,10 +53,13 @@ function make401Response(wwwAuth) {
|
|
|
41
53
|
id: null
|
|
42
54
|
}, {
|
|
43
55
|
status: 401,
|
|
44
|
-
headers: {
|
|
56
|
+
headers: {
|
|
57
|
+
...addExposeHeader(corsHeaders, "WWW-Authenticate"),
|
|
58
|
+
"WWW-Authenticate": wwwAuth
|
|
59
|
+
}
|
|
45
60
|
});
|
|
46
61
|
}
|
|
47
|
-
function send401Node(res, wwwAuth, message) {
|
|
62
|
+
function send401Node(res, wwwAuth, message, corsHeaders) {
|
|
48
63
|
const body = JSON.stringify({
|
|
49
64
|
jsonrpc: "2.0",
|
|
50
65
|
error: {
|
|
@@ -53,13 +68,21 @@ function send401Node(res, wwwAuth, message) {
|
|
|
53
68
|
},
|
|
54
69
|
id: null
|
|
55
70
|
});
|
|
71
|
+
const existingExposedHeaders = res.get?.("Access-Control-Expose-Headers") ?? res.getHeader?.("Access-Control-Expose-Headers") ?? res.getHeader?.("access-control-expose-headers");
|
|
72
|
+
const headers = {
|
|
73
|
+
...addExposeHeader({
|
|
74
|
+
...corsHeaders,
|
|
75
|
+
...existingExposedHeaders ? { "Access-Control-Expose-Headers": Array.isArray(existingExposedHeaders) ? existingExposedHeaders.join(", ") : String(existingExposedHeaders) } : {}
|
|
76
|
+
}, "WWW-Authenticate"),
|
|
77
|
+
"WWW-Authenticate": wwwAuth
|
|
78
|
+
};
|
|
56
79
|
if (typeof res.set === "function") {
|
|
57
|
-
res.set(
|
|
80
|
+
for (const [name, value] of Object.entries(headers)) res.set(name, value);
|
|
58
81
|
res.status?.(401).json(JSON.parse(body));
|
|
59
82
|
} else if (typeof res.writeHead === "function") {
|
|
60
83
|
res.writeHead(401, {
|
|
61
84
|
"Content-Type": "application/json",
|
|
62
|
-
|
|
85
|
+
...headers
|
|
63
86
|
});
|
|
64
87
|
res.end?.(body);
|
|
65
88
|
}
|
|
@@ -159,10 +182,10 @@ function createMcpResourceClient(options) {
|
|
|
159
182
|
try {
|
|
160
183
|
session = await verifyRequest(req);
|
|
161
184
|
} catch (error) {
|
|
162
|
-
if (isDpopBindingError(error)) return make401Response(makeDpopWWWAuthenticate(dpopSigningAlgorithms));
|
|
185
|
+
if (isDpopBindingError(error)) return make401Response(makeDpopWWWAuthenticate(dpopSigningAlgorithms), corsHeaders);
|
|
163
186
|
throw error;
|
|
164
187
|
}
|
|
165
|
-
if (!session) return make401Response(selectChallenge(req.headers.get("Authorization"), req.headers.has("DPoP")));
|
|
188
|
+
if (!session) return make401Response(selectChallenge(req.headers.get("Authorization"), req.headers.has("DPoP")), corsHeaders);
|
|
166
189
|
return fn(req, session);
|
|
167
190
|
};
|
|
168
191
|
};
|
|
@@ -217,13 +240,13 @@ function createMcpResourceClient(options) {
|
|
|
217
240
|
session = await verifyRequest(request);
|
|
218
241
|
} catch (error) {
|
|
219
242
|
if (isDpopBindingError(error)) {
|
|
220
|
-
send401Node(res, makeDpopWWWAuthenticate(dpopSigningAlgorithms), "Invalid or expired token");
|
|
243
|
+
send401Node(res, makeDpopWWWAuthenticate(dpopSigningAlgorithms), "Invalid or expired token", corsHeaders);
|
|
221
244
|
return;
|
|
222
245
|
}
|
|
223
246
|
throw error;
|
|
224
247
|
}
|
|
225
248
|
if (!session) {
|
|
226
|
-
send401Node(res, selectChallenge(authHeader, !!getHeader(req, "dpop")), authHeader ? "Invalid or expired token" : "Unauthorized: Authentication required");
|
|
249
|
+
send401Node(res, selectChallenge(authHeader, !!getHeader(req, "dpop")), authHeader ? "Invalid or expired token" : "Unauthorized: Authentication required", corsHeaders);
|
|
227
250
|
return;
|
|
228
251
|
}
|
|
229
252
|
req.mcpSession = session;
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,6 @@ import { DpopReplayReservations, DpopReplayStore, verifyAccessTokenRequest } fro
|
|
|
3
3
|
import { JWTPayload } from "jose";
|
|
4
4
|
import { Awaitable } from "@better-auth/core";
|
|
5
5
|
import { BetterAuthOptions } from "better-auth/types";
|
|
6
|
-
|
|
7
6
|
//#region src/handler.d.ts
|
|
8
7
|
/**
|
|
9
8
|
* A request middleware handler that verifies an MCP access token and responds
|
|
@@ -11,10 +10,11 @@ import { BetterAuthOptions } from "better-auth/types";
|
|
|
11
10
|
*
|
|
12
11
|
* @external
|
|
13
12
|
*/
|
|
14
|
-
declare const mcpHandler: (
|
|
15
|
-
|
|
13
|
+
declare const mcpHandler: (
|
|
14
|
+
/** Verifier options. `audience` must match the protected resource identifier. */
|
|
16
15
|
verifyOptions: Parameters<typeof verifyAccessTokenRequest>[1], handler: (req: Request, jwt: JWTPayload) => Awaitable<Response>, opts?: {
|
|
17
|
-
/** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
16
|
+
/** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
17
|
+
resourceMetadataMappings: Record<string, string>;
|
|
18
18
|
}) => (req: Request) => Promise<Response>;
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/plugin.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -43,7 +43,7 @@ const mcpHandler = (verifyOptions, handler, opts) => {
|
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/plugin.ts
|
|
45
45
|
const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
46
|
-
const AUTHORIZATION_SERVER_ONLY_SCOPES = new Set([
|
|
46
|
+
const AUTHORIZATION_SERVER_ONLY_SCOPES = /* @__PURE__ */ new Set([
|
|
47
47
|
"openid",
|
|
48
48
|
"profile",
|
|
49
49
|
"email",
|
|
@@ -133,7 +133,7 @@ const mcp = (options) => {
|
|
|
133
133
|
} catch {
|
|
134
134
|
resourcePath = "";
|
|
135
135
|
}
|
|
136
|
-
if (!new Set([PROTECTED_RESOURCE_METADATA_PATH, `${PROTECTED_RESOURCE_METADATA_PATH}${resourcePath}`]).has(requestPath)) return;
|
|
136
|
+
if (!(/* @__PURE__ */ new Set([PROTECTED_RESOURCE_METADATA_PATH, `${PROTECTED_RESOURCE_METADATA_PATH}${resourcePath}`])).has(requestPath)) return;
|
|
137
137
|
if (request.method !== "GET" && request.method !== "HEAD") return { response: new Response(null, {
|
|
138
138
|
status: 405,
|
|
139
139
|
headers: { Allow: "GET, HEAD" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/mcp",
|
|
3
|
-
"version": "1.7.0-rc.
|
|
3
|
+
"version": "1.7.0-rc.2",
|
|
4
4
|
"description": "Model Context Protocol (MCP) plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,22 +63,22 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"jose": "^6.1.3",
|
|
66
|
-
"@better-auth/oauth-provider": "^1.7.0-rc.
|
|
66
|
+
"@better-auth/oauth-provider": "^1.7.0-rc.2"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
70
70
|
"better-call": "1.3.7",
|
|
71
71
|
"listhen": "1.9.0",
|
|
72
|
-
"tsdown": "0.
|
|
73
|
-
"
|
|
74
|
-
"better-auth": "1.7.0-rc.
|
|
72
|
+
"tsdown": "0.22.7",
|
|
73
|
+
"better-auth": "1.7.0-rc.2",
|
|
74
|
+
"@better-auth/core": "1.7.0-rc.2"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@better-auth/utils": "0.4.2",
|
|
78
78
|
"@better-fetch/fetch": "1.3.1",
|
|
79
79
|
"better-call": "1.3.7",
|
|
80
|
-
"
|
|
81
|
-
"better-auth": "^1.7.0-rc.
|
|
80
|
+
"better-auth": "^1.7.0-rc.2",
|
|
81
|
+
"@better-auth/core": "^1.7.0-rc.2"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsdown",
|