@better-auth/mcp 1.7.0-rc.0 → 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.
@@ -1,5 +1,4 @@
1
1
  import { McpResourceClient, McpResourceClientOptions, McpSession } from "./index.mjs";
2
-
3
2
  //#region src/client/adapters.d.ts
4
3
  interface HonoContext {
5
4
  req: {
@@ -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
- return `${PROTECTED_RESOURCE_METADATA_PATH}${resourceUrl.pathname === "/" ? "" : resourceUrl.pathname.replace(/\/$/, "")}`;
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) return unauthorized(c, parseAccessTokenAuthorization(authHeader)?.scheme === "DPoP" || !!c.req.header("DPoP") ? dpopChallenge : `Bearer resource_metadata="${resourceMetadata}"`, authHeader ? "Invalid or expired token" : "Unauthorized: Authentication required");
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" }));
@@ -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
  };
@@ -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 make401Response(wwwAuth) {
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: { "WWW-Authenticate": wwwAuth }
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("WWW-Authenticate", wwwAuth);
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
- "WWW-Authenticate": wwwAuth
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: (/** Verifier options. `audience` must match the protected resource identifier. */
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 */resourceMetadataMappings: Record<string, string>;
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.0",
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.0"
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.21.1",
73
- "@better-auth/core": "1.7.0-rc.0",
74
- "better-auth": "1.7.0-rc.0"
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
- "@better-auth/core": "^1.7.0-rc.0",
81
- "better-auth": "^1.7.0-rc.0"
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",