Integrating Kinde in Remote MCP Server
Hi, I'm developing a remote MCP server for use in Claude, Cursor etc. and trying to integrate Kinde. The MCP auth flow (https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) needs to comply with OAuth2.0 Authorization Server Metadata RFC 8414 and Dynamic Client Registration Protocol RFC 7591
I have had some luck but the flow is failing with Kinde's /.well_known/oauth-authorization-server endpoint because it doesn't provide a registration_endpoint field (dynamic client registration). I've worked around this by defining my app as the security "issuer", returning my own oauth-authorization-server response and explicitly defining /register, /authorize, and /token endpoints. In /register I return mockup client ID and secret. It works but now I need to integrate Kinde.
During the connection process in Claude.ai (or Cursor.. etc.) I want the user to be directed to Kinde's login. I think my app's /.well_known/oauth-authorization-server endpoint will still be needed as a proxy but I'm not 100%. I believe I will need to delegate the /authorize and /token endpoints and I'm unsure of what I will need to include in /register - forward to /login?? .
I have setup a Kinde backend application and tried delegating /authorize to Kinde's /oauth2/auth endpoint but I'm getting an error in that the "callback" is not configured in Kinde. But this is the client's callback, which obviously I can't include because it's always changing. Maybe I need to use single-page PKCE application ??
I've previously implemented multiple nextjs apps with Kinde so I have some knowledge of Kinde but I'm way out of my league on this and need some help.
Thanks
Dave
20 Replies
Hey Dave,
Thanks for reaching out - great timing, we’re actually exploring some MCP server solutions ourselves at the moment.
Before we dive into possible solutions, I just want to make sure we're aligned on what you're trying to achieve.
Are you looking to build an MCP server for tools like Claude or Cursor so LLMs can interact directly with your Kinde instance (e.g. via the Management API)?
Or is the goal to have LLMs act on behalf of end-users and connect to your app’s backend through the MCP server?
Keen to clarify so we can support you properly.
Hi Oliver, firstly, congrats on the Billing release. I've been impressed with the exposure I've had but will dive in deeper soon.
If you recall, I have a multi-tenant, multi-app application for creating AI agent solutions. The main use case is for agents to "discover" and connect to other agents via MCP. However, interacting with agents from Claude or Cursor is also relevant. In the case of a user connecting within Claude, I want to support Authorization Code but for agent-agent communication I need to support Client Credentials. FYI, my remote MCP server is using the more recent Streamable HTTP for transport but that doesn't affect the issue.
I did more reearch and found that the MCP server and authentication server need to have the same domain for browser-based access and session cookies. I might be wrong on this (or ChatGPT is ;-)), but that caused me to implement my own /authorize, /token, /register and /.well_known methods and was able to get the basics working. Where I'm at now is my server is behaving as the auth issuer and I'm trying to integrate Kinde as the identity provider i.e. delegate login/register.
Hey Dave,
Thanks for your comments on our billing release.
Also, thanks for elaborating further on your use-case.
I will have to dig into your scenario further with my teammates and get back to you once I have more info to share.
Thanks for your comments on our billing release.
Also, thanks for elaborating further on your use-case.
I will have to dig into your scenario further with my teammates and get back to you once I have more info to share.
Thanks, Oliver. FYI, I am seeing a couple of subtle differences in how MCP clients are connecting. For example, Claude.ai is not calling /register while Claude Desktop does if the integration is added through the json config file and using mcp-remote. Regardless, the spec says that the authorization server needs to support dynamic discovery i.e. server should expect client to call /register. I also cannot find any formal rules on providing the client_id. I've been trying adding it as a query string parameter and also embedding in /.well_known/oauth-protected-resource response to see if clients use it and dismiss call /register ....but this is also not part of the spec.
Hey Dave,
Thanks again for reaching out and sharing your findings.
Unfortunately, I am not sure we are able to be of much help in achieve your use-case here for agents to "discover" and connect to other agents via MCP.
Looks like you have done good research yourself, I know others in this community are working on MCPs, so don't be afraid to reach out to others in #general to see how others are building MCPs
Thanks again for reaching out and sharing your findings.
Unfortunately, I am not sure we are able to be of much help in achieve your use-case here for agents to "discover" and connect to other agents via MCP.
Looks like you have done good research yourself, I know others in this community are working on MCPs, so don't be afraid to reach out to others in #general to see how others are building MCPs
Hi Oliver, all good. I was able to get it working integrated with Kinde as follows:- my mcp server also behaves as an authorization server (I implemented /.well_known, /register, /authorize, /token endpoints). During /authorize I call the Kinde login to authenticate user and get an auth code and pass back. When mcp client requests /token, I call Kinde's token endpoint to the get it's access token, then I wrap and sign it and return it to the mcp client. When the client makes a request passing the wrapped token, I unwrap it, extract the Kinde token and pass to the tool API, which is secured by Kinde. I'm 98% complete with one issue to sort out re. audiences. But, this design seems to cover everything so far and hopefully I haven't missed anything re. security holes since I'm way out of my league on this stuff. Hopefully this is helpful for others. Thx. Dave.
Sounds like you have made good progress.
Let us know if we can help with anything else.
Let us know if we can help with anything else.
Need help on last piece.... My nodejs mcp-server (acting as auth server) has a backend Kinde app. When I request a 'tool' in Claude mcp client, if not authenticated (connected) it will trigger login with Kinde. After handshaking I have a Kinde access token including api audience. I use this access token to request an api route from the nextjs api-server supported by a Kinde backend app, which has been setup with api audience. Both mcp-server and api-server are on the same domain, authorized with the same API audience. The "withAuth" in the api-server middleware.ts returns nothing. However, when I unpack and validate the access token received by the api-server I can see the correct audience and it hasn't expired. KINDE_DEBUG=true. Not printing anything. I call the following code in the middleware to validate the token. JWKS is from Kinde. It confirms the token is valid while withAuth seems to reject it.
UPDATE: I configured the api-server and mcp-server to use the same Kinde backend application but no change. How can I get meaningful debug info from withAuth? KINDE_DEBUG=true is not logging anything.
Hi, @Dave
Here are some troubleshooting steps for this case,
1. Check Your Middleware Implementation, the correct way to implement withAuth is:
import { withAuth } from "@kinde-oss/kinde-auth-nextjs/middleware";
export default withAuth(async function middleware(req) {
console.log("look at me", req.kindeAuth);
});
export const config = {
matcher: ["/admin"],
};
If that doesn't work, try this alternative approach:
import { withAuth } from "@kinde-oss/kinde-auth-nextjs/middleware";
export default function middleware(req) {
return withAuth(req);
}
export const config = {
matcher: ["/admin"],
};
2. Migration Considerations
If you're using a newer version of the Next.js SDK, note that handleAuth is now imported from a different path:
import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";
export const GET = handleAuth();
3. Alternative Approach for API Routes
Since your manual JWT verification works correctly, consider implementing your own middleware instead of relying on withAuth. Your verifyToken function is already validating the token properly against the JWKS.
You could create a custom middleware that:
- Extracts the Bearer token from the Authorization header
- Uses your existing verifyToken function
- Handles the authentication logic directly
4. Check Token Format
Also Ensure your API server is receiving the token in the correct format. The token should be passed in the Authorization header as:
Authorization: Bearer <access_token>
5. Version-Specific Issues
The withAuth were addressed in version 2.2.5-1 and later. Ensure you're using a recent version of the @kinde-oss/kinde-auth-nextjs package.
If you need to use withAuth specifically, try updating to the latest version of the SDK and use the corrected debug environment variable KINDE_DEBUG_MODE=true to get more detailed logging.
Let me know if I need to clarify anything.
ThanksHi, please see attached response.
Another question, only the access token needs to be sent in the header, correct? I did not capture or attach an Id token.
Hi Dave,
Thanks for the update and detailed debugging.
The issue you're experiencing stems from a fundamental difference in how
The
1. Continue Using Your Custom
Thanks, Let me know the updates
withAuth
handles authentication versus how you're authenticating users via Bearer tokens in the Authorization header.The
withAuth
middleware in @kinde-oss/kinde-auth-nextjs
SDK is designed primarily for web applications using cookie-based sessions. Here's why it's not working as expected in your setup:
- withAuth
expects the access token and ID token to be present in cookies, not in the Authorization
header.
- Your use case involves API-to-API communication, where tokens are passed as Authorization: Bearer <access_token>
, as per OAuth2 standards.
- Therefore, even though you're passing a valid token, withAuth
doesn't see it, logs getAccessToken: invalid token or token is missing
, and redirects to the login page.
This mismatch explains why your manual verifyToken()
function works (because it does look in the header), while withAuth
silently fails.
Since your setup relies on API-to-API calls, the correct path forward is to use a JWT validation middleware that mimics Express patterns. You can achieve this in two ways:1. Continue Using Your Custom
verifyToken()
Middleware:
You're already manually verifying the token using jwtVerify()
with Kinde’s JWKS, issuer, and audience. This is a secure and standards-compliant approach.
You can keep using this method in your middleware.ts
or in each API route like so:
2. Use Kinde’s @kinde-oss/kinde-node-express
JWT Middleware
If you prefer to lean on an official SDK for JWT validation, use the @kinde-oss/kinde-node-express
package. This works great even in a Next.js app/
directory setup if you simulate an Express-like request object.
Thanks, Let me know the updates
Super excellent response, Abdiwak! Thank you!! The browser user (mcp client like Claude) utilizes a tool from my mcp-server, which uses Kinde login to authenticate them. Given that I will also have the ID token from authentication, If I pass ID token and access token in cookie header to the api server, then withAuth should work, correct? .... or am I totally missing something?? I'm trying to maintain user-context, hence the reason for pursuing this path.
Thanks, Dave
You're absolutely on the right track.
If your MCP server passes both the ID token and Access token to the API server via cookies, then
The middleware expects these cookies: -
Once these cookies are set in the browser,
You're absolutely on the right track.
If your MCP server passes both the ID token and Access token to the API server via cookies, then
withAuth
should work as expected, as long as the cookies match the exact format that Kinde expects.The middleware expects these cookies: -
kinde_access_token
- kinde_id_token
- (optional) kinde_refresh_token
Note: Use SameSite=None
instead of Strict
if your clients (like Claude) are accessing from a different origin.Once these cookies are set in the browser,
withAuth
middleware will:
- Automatically extract the tokens from the cookies
- Validate them against your Kinde backend
- Populate req.kindeAuth
with full user context (ID, orgs, permissions, etc.)
A Few Things to Watch Out For
- This only works if your MCP client actually runs in a browser context (like Claude Web or Cursor Web) and supports cookies.
- If the client is headless or operates server-side only, then cookie-based auth won’t work, and Bearer token validation is the right approach (which you’ve already implemented correctly with verifyToken()
).
- Also make sure you're using the latest version of the SDK to avoid cookie parsing issues.
Let me know if this helps, ThanksAnother great answer! Thank you Abdiwak 🙂 "This only works if your MCP client actually runs in a browser context (like Claude Web or Cursor Web) and supports cookies" - so you're essentially saying grant type negotiation with Kinde needs to be Auth Code, correct?
I'm going through this exercise of developing my own auth server because Kinde doesnt support dynamic registration as required by the MCP spec. Now, I totally understand why. However, here's a possible workaorund... I seem to recall reading in the Oauth spec that URL search params need to be preserved in interactions. So, if a user sets up an MCP server in a client with a url, say http://mymcpserver.com/mcp/?client_id=123 and Kinde's /register endpoint simply takes the search string and returns it in its response, then it satisfies the spec, is little effort to implement, and carries little risk. THe client_id in this context, would likely be the client_id of a Kinde app that the mcp server would use internally. What do you think of this approach?
I'm going through this exercise of developing my own auth server because Kinde doesnt support dynamic registration as required by the MCP spec. Now, I totally understand why. However, here's a possible workaorund... I seem to recall reading in the Oauth spec that URL search params need to be preserved in interactions. So, if a user sets up an MCP server in a client with a url, say http://mymcpserver.com/mcp/?client_id=123 and Kinde's /register endpoint simply takes the search string and returns it in its response, then it satisfies the spec, is little effort to implement, and carries little risk. THe client_id in this context, would likely be the client_id of a Kinde app that the mcp server would use internally. What do you think of this approach?
Hi, Dave Thanks Again
You're absolutely correct.
Authorization Code Flow is the appropriate OAuth 2.0 grant type when working with browser-based MCP clients like Claude Web. This flow supports user interaction and safely handles redirects and client secrets on the server side which is ideal for your setup.
Your idea of implementing a minimal
For your current implementation, you're already handling the Authorization Code Flow correctly by delegating to Kinde's
Thanks, Let me know
You're absolutely correct.
Authorization Code Flow is the appropriate OAuth 2.0 grant type when working with browser-based MCP clients like Claude Web. This flow supports user interaction and safely handles redirects and client secrets on the server side which is ideal for your setup.
Your idea of implementing a minimal
/register
endpoint that echoes back client_id
and static metadata from URL search parameters is not only valid but also cleverly compliant with RFC 7591. It Satisfies the MCP Spec and also maintains kinde's security model.For your current implementation, you're already handling the Authorization Code Flow correctly by delegating to Kinde's
/oauth2/auth
endpoint and managing the token exchange. Your proxy approach maintains the required same-domain requirement for cookies while leveraging Kinde's robust authentication infrastructure.Thanks, Let me know
Hi Abdiwak,
Thanks for all your valuable help on this. Givem my suggestion re. /register, is this something that could be considered for the product? If implemented I believe it would allow Kinde to be used as the auth issuer in an MCP scenario rather than have to custom code it.
Thx, Dave
P.S. FYI, Stytch supports dynamic registration. I don't need that but something to make it MCP compliant would be good. Thx.
Hi Dave,
Thank you for the thoughtful suggestion and for all the detailed context you've shared throughout this process.
Your proposed
The team is actively exploring support for Dynamic Client Registration. I’ve also flagged this internally. Really appreciate you pushing the boundaries of what's possible with Kinde. If you have any other suggestions or run into further friction points, don’t hesitate to reach out.
Thanks
/register
passthrough approach is a clever and pragmatic way to meet MCP spec requirements while keeping things secure and lightweight.The team is actively exploring support for Dynamic Client Registration. I’ve also flagged this internally. Really appreciate you pushing the boundaries of what's possible with Kinde. If you have any other suggestions or run into further friction points, don’t hesitate to reach out.
Thanks
Hello there,
I'm also developing MCP servers and I would very appreciate to have dynamic client registration in Kinde
Is it part of the roadmap? Is there an ETA regarding this feature?
Hi Paul,
Regarding Dynamic Client Registration, The proof of concept is successfully completed and it should be live very soon.
I will update you as soon as it’s available.
Thanks!