MCP plugin advertises incorrect jwks_uri when using useJWTPlugin: true #2554

Closed
opened 2026-03-13 10:02:46 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @jurvis on GitHub (Dec 17, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure Better-Auth with the mcp plugin as so: mcp({ oidcConfig: { useJWTPlugin: true } }) as well as the jwt plugin
  2. Go to resource metadata URL, ROOT_URL/.well-known/oauth-protected-resource

Current vs. Expected behavior

Current Behavior

When configuring betterAuth with mcp({ oidcConfig: { useJWTPlugin: true } }), the MCP plugin still advertises jwks_uri as /mcp/jwks in the OAuth protected resource metadata, even though:

  • The useJWTPlugin: true flag explicitly delegates JWT signing to the JWT plugin
  • The JWT plugin serves the JWKS at /jwks (not /mcp/jwks)
  • There's no /mcp/jwks endpoint when using the JWT plugin

Expected Behavior

When useJWTPlugin: true, the MCP plugin should automatically derive the correct jwks_uri from the JWT plugin's configured path, not the hardcoded /mcp/jwks.

A workaround exists where we can override using oidcConfig.metadata.jwks_uri, but users should not need to manually align those values.

What version of Better Auth are you using?

1.4.6

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000",
    "release": "25.1.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.47 GB"
  },
  "node": {
    "version": "v24.11.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^19.2.1"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    }
  ],
  "betterAuth": {
    "version": "^1.4.6",
    "config": {
      "database": {
        "_events": {},
        "_eventsCount": 0,
        "options": {
          "connectionString": "[REDACTED]",
          "max": 10,
          "min": 0,
          "maxUses": null,
          "allowExitOnIdle": false,
          "maxLifetimeSeconds": 0,
          "idleTimeoutMillis": 10000
        },
        "_clients": [],
        "_idle": [],
        "_expired": {},
        "_pendingQueue": [],
        "ending": false,
        "ended": false
      },
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": false
      },
      "socialProviders": {
        "google": {
          "clientId": "",
          "clientSecret": ""
        }
      },
      "disabledPaths": [
        "/token"
      ],
      "plugins": [
        {
          "name": "mcp",
          "config": {
            "id": "mcp",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectUrls": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        {
          "name": "jwt",
          "config": {
            "id": "jwt",
            "options": {
              "jwt": {
                "expirationTime": "1h"
              },
              "disableSettingJwtHeader": true
            },
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "jwks": {
                "fields": {
                  "publicKey": {
                    "type": "string",
                    "required": true
                  },
                  "privateKey": {
                    "type": "string",
                    "required": true
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": false
                  }
                }
              }
            }
          }
        },
        {
          "name": "open-api",
          "config": {
            "id": "open-api",
            "endpoints": {}
          }
        }
      ],
      "secret": "[REDACTED]",
      "baseURL": "http://localhost:5173"
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  ...
  plugins: [
    mcp({
      loginPage: '/login',
      oidcConfig: {
        loginPage: '/login',
        requirePKCE: true,
        allowPlainCodeChallengeMethod: false,
        useJWTPlugin: true,
        scopes: ['files:read'],
        metadata: {
          jwks_uri: `${ROOT_URL}/api/auth/jwks`,
        },
      },
    }),
    jwt({
      jwt: {
        expirationTime: '1h',
      },
      disableSettingJwtHeader: true,
    }),
    openAPI(),
  ],
 ...
});

Additional context

No response

Originally created by @jurvis on GitHub (Dec 17, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure Better-Auth with the mcp plugin as so: `mcp({ oidcConfig: { useJWTPlugin: true } })` as well as the `jwt` plugin 2. Go to resource metadata URL, `ROOT_URL/.well-known/oauth-protected-resource` ### Current vs. Expected behavior ## Current Behavior When configuring betterAuth with `mcp({ oidcConfig: { useJWTPlugin: true } })`, the MCP plugin still advertises `jwks_uri` as `/mcp/jwks` in the OAuth protected resource metadata, even though: - The `useJWTPlugin: true` flag explicitly delegates JWT signing to the JWT plugin - The JWT plugin serves the JWKS at /jwks (not /mcp/jwks) - There's no /mcp/jwks endpoint when using the JWT plugin ## Expected Behavior When `useJWTPlugin: true`, the MCP plugin should automatically derive the correct jwks_uri from the JWT plugin's configured path, not the hardcoded `/mcp/jwks`. A workaround exists where we can override using `oidcConfig.metadata.jwks_uri`, but users should not need to manually align those values. ### What version of Better Auth are you using? 1.4.6 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000", "release": "25.1.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "32.00 GB", "freeMemory": "0.47 GB" }, "node": { "version": "v24.11.1", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": [ { "name": "react", "version": "^19.2.1" } ], "databases": [ { "name": "pg", "version": "^8.16.3" } ], "betterAuth": { "version": "^1.4.6", "config": { "database": { "_events": {}, "_eventsCount": 0, "options": { "connectionString": "[REDACTED]", "max": 10, "min": 0, "maxUses": null, "allowExitOnIdle": false, "maxLifetimeSeconds": 0, "idleTimeoutMillis": 10000 }, "_clients": [], "_idle": [], "_expired": {}, "_pendingQueue": [], "ending": false, "ended": false }, "emailAndPassword": { "enabled": true, "requireEmailVerification": false }, "socialProviders": { "google": { "clientId": "", "clientSecret": "" } }, "disabledPaths": [ "/token" ], "plugins": [ { "name": "mcp", "config": { "id": "mcp", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } } } }, { "name": "jwt", "config": { "id": "jwt", "options": { "jwt": { "expirationTime": "1h" }, "disableSettingJwtHeader": true }, "endpoints": {}, "hooks": { "after": [ {} ] }, "schema": { "jwks": { "fields": { "publicKey": { "type": "string", "required": true }, "privateKey": { "type": "string", "required": true }, "createdAt": { "type": "date", "required": true }, "expiresAt": { "type": "date", "required": false } } } } } }, { "name": "open-api", "config": { "id": "open-api", "endpoints": {} } } ], "secret": "[REDACTED]", "baseURL": "http://localhost:5173" } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ ... plugins: [ mcp({ loginPage: '/login', oidcConfig: { loginPage: '/login', requirePKCE: true, allowPlainCodeChallengeMethod: false, useJWTPlugin: true, scopes: ['files:read'], metadata: { jwks_uri: `${ROOT_URL}/api/auth/jwks`, }, }, }), jwt({ jwt: { expirationTime: '1h', }, disableSettingJwtHeader: true, }), openAPI(), ], ... }); ``` ### Additional context _No response_
GiteaMirror added the plugin label 2026-03-13 10:02:46 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 17, 2025):

It appears that when using useJWTPlugin:true the MCP plugin still reports a hardcoded jwks_uri (/mcp/jwks) instead of dynamically using the JWT plugin’s endpoint (/api/auth/jwks in your config). A temporary solution is to override the metadata.jwks_uri manually in oidcConfig, but ideally the plugin should derive the value based on the JWT plugin configuration.

Have you tried the manual override workaround, or do you need further troubleshooting steps? tag @better-auth-agent if you would like an answer from the Agent.

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Dec 17, 2025): It appears that when using useJWTPlugin:true the MCP plugin still reports a hardcoded jwks_uri (/mcp/jwks) instead of dynamically using the JWT plugin’s endpoint (/api/auth/jwks in your config). A temporary solution is to override the metadata.jwks_uri manually in oidcConfig, but ideally the plugin should derive the value based on the JWT plugin configuration. Have you tried the manual override workaround, or do you need further troubleshooting steps? tag @better-auth-agent if you would like an answer from the Agent. _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 17, 2025):

Hi @jurvis! I'm Dosu and I’m helping the better-auth team.

This is a known limitation in Better Auth. When you set useJWTPlugin: true in the MCP plugin, it does not automatically update the advertised jwks_uri to match the JWT plugin’s endpoint. Instead, MCP continues to advertise the hardcoded /mcp/jwks path, even though the JWT plugin serves JWKS at /jwks (or a custom path if configured). There’s no logic in MCP to detect or derive the JWT plugin’s JWKS endpoint automatically—manual override via oidcConfig.metadata.jwks_uri is currently required for correct behavior. This is recognized as a bug and discussed in several issues and PRs (example, example).

For now, the only reliable workaround is to manually set jwks_uri in your MCP config to match the JWT plugin’s endpoint. Ideally, MCP should automatically align this value when delegating JWT signing, but that’s not implemented yet.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Dec 17, 2025): <!-- Greeting --> Hi @jurvis! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known limitation in Better Auth. When you set `useJWTPlugin: true` in the MCP plugin, it does not automatically update the advertised `jwks_uri` to match the JWT plugin’s endpoint. Instead, MCP continues to advertise the hardcoded `/mcp/jwks` path, even though the JWT plugin serves JWKS at `/jwks` (or a custom path if configured). There’s no logic in MCP to detect or derive the JWT plugin’s JWKS endpoint automatically—manual override via `oidcConfig.metadata.jwks_uri` is currently required for correct behavior. This is recognized as a bug and discussed in several issues and PRs ([example](https://github.com/better-auth/better-auth/blob/e76defb786ba2d4e3695d6b15251612d075f13b2/packages/better-auth/src/plugins/mcp/index.ts#L39-L947), [example](https://github.com/better-auth/better-auth/pull/6397)). For now, the only reliable workaround is to manually set `jwks_uri` in your MCP config to match the JWT plugin’s endpoint. Ideally, MCP should automatically align this value when delegating JWT signing, but that’s not implemented yet. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/20a87313-c7ff-4312-859c-3210c0b2fa2b?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20limitation%20in%20Better%20Auth.%20When%20you%20set%20%60useJWTPlugin%3A%20true%60%20in%20the%20MCP%20plugin%2C%20it%20does%20not%20automatically%20update%20the%20advertised%20%60jwks_uri%60%20to%20match%20the%20JWT%20plugin%E2%80%99s%20endpoint.%20Instead%2C%20MCP%20continues%20to%20advertise%20the%20hardcoded%20%60/mcp/jwks%60%20path%2C%20even%20though%20the%20JWT%20plugin%20serves%20JWKS%20at%20%60/jwks%60%20%28or%20a%20custom%20path%20if%20configured%29.%20There%E2%80%99s%20no%20logic%20in%20MCP%20to%20detect%20or%20derive%20the%20JWT%20plugin%E2%80%99s%20JWKS%20endpoint%20automatically%E2%80%94manual%20override%20via%20%60oidcConfig.metadata.jwks_uri%60%20is%20currently%20required%20for%20correct%20behavior.%20This%20is%20recognized%20as%20a%20bug%20and%20discussed%20in%20several%20issues%20and%20PRs%20%28%5Bexample%5D%28https%3A//github.com/better-auth/better-auth/blob/e76defb786ba2d4e3695d6b15251612d075f13b2/packages/better-auth/src/plugins/mcp/index.ts%23L39-L947%29%2C%20%5Bexample%5D%28https%3A//github.com/better-auth/better-auth/pull/6397%29%29.%0A%0AFor%20now%2C%20the%20only%20reliable%20workaround%20is%20to%20manually%20set%20%60jwks_uri%60%20in%20your%20MCP%20config%20to%20match%20the%20JWT%20plugin%E2%80%99s%20endpoint.%20Ideally%2C%20MCP%20should%20automatically%20align%20this%20value%20when%20delegating%20JWT%20signing%2C%20but%20that%E2%80%99s%20not%20implemented%20yet.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6817)
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 18, 2025):

Hi there! I haven’t seen a specific PR addressing this exact behavior in our records. It appears that when using the MCP plugin with the useJWTPlugin flag, the advertised jwks_uri remains hardcoded instead of being derived from the JWT plugin’s endpoint. A temporary workaround is to override the jwks_uri in your oidcConfig metadata. If you haven’t already, please try that approach; if it resolves your issue, it might also help narrow the bug. Otherwise, it would be best to open a new issue and tag our maintainers—@bekacru and @ping__—so we can look into aligning the configuration automatically. Have you had a chance to test the override, and did it work for you? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Dec 18, 2025): Hi there! I haven’t seen a specific PR addressing this exact behavior in our records. It appears that when using the MCP plugin with the useJWTPlugin flag, the advertised jwks_uri remains hardcoded instead of being derived from the JWT plugin’s endpoint. A temporary workaround is to override the jwks_uri in your oidcConfig metadata. If you haven’t already, please try that approach; if it resolves your issue, it might also help narrow the bug. Otherwise, it would be best to open a new issue and tag our maintainers—@bekacru and @ping__—so we can look into aligning the configuration automatically. Have you had a chance to test the override, and did it work for you? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@Ridhim-RR commented on GitHub (Dec 18, 2025):

looking into this

@Ridhim-RR commented on GitHub (Dec 18, 2025): looking into this
Author
Owner

@dvanmali commented on GitHub (Dec 24, 2025):

Hi all, we released the the new OAuth Provider Plugin which fixes multiple MCP/OIDC plugin issues including this one. Feel free to let us know how it works :)

@dvanmali commented on GitHub (Dec 24, 2025): Hi all, we released the the new [OAuth Provider Plugin](https://www.better-auth.com/docs/plugins/oauth-provider) which fixes multiple MCP/OIDC plugin issues including this one. Feel free to let us know how it works :)
Author
Owner

@jurvis commented on GitHub (Dec 24, 2025):

@dvanmali I figured there was a chance that PR would tie up some of these loose ends :) congratulations, and thank you for landing such a herculean effort!

@jurvis commented on GitHub (Dec 24, 2025): @dvanmali I figured there was a chance that PR would tie up some of these loose ends :) congratulations, and thank you for landing such a herculean effort!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2554