[GH-ISSUE #2914] MCP OAuth flow shows {redirect: true, url: …} instead of a HTTP Redirect Code #9395

Closed
opened 2026-04-13 04:50:30 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @wottpal on GitHub (Jun 5, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2914

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Hey there, I followed the MCP tutorial/docs and developed my own MCP server with better-auth, vercel/mcp-adapter, mcp-remote (as advised). And it works except one big quirk (see below).

Current vs. Expected behavior

Only one big quirk: In the OAuth flow, after successful sign-in, it shows a JSON like in the screenshot below and the user must click on the link manually to finish the signup (basically everyone misses that rn).

Image

If I click on the link, it finishes the process successfully. – I would expect instead a HTTP redirect status code that does the redirect automatically.

CC @geelen Just in case this is a mcp-remote problem

What version of Better Auth are you using?

1.2.9 beta

Provide environment information

macOS
Arc

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

Backend

Auth config (if applicable)


Additional context

No response

Originally created by @wottpal on GitHub (Jun 5, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2914 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Hey there, I followed the MCP tutorial/docs and developed my own MCP server with better-auth, vercel/mcp-adapter, mcp-remote (as advised). And it works except one big quirk (see below). ### Current vs. Expected behavior Only one big quirk: In the OAuth flow, after successful sign-in, it shows a JSON like in the screenshot below and the user **must click on the link manually** to finish the signup (basically everyone misses that rn). ![Image](https://github.com/user-attachments/assets/b392a5fe-81c8-444a-9125-34437e5aef57) If I click on the link, it finishes the process successfully. – I would expect instead a HTTP redirect status code that does the redirect automatically. CC @geelen Just in case this is a `mcp-remote` problem ### What version of Better Auth are you using? 1.2.9 beta ### Provide environment information ```bash macOS Arc ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 04:50:30 -05:00
Author
Owner

@dkokotov commented on GitHub (Jun 9, 2025):

@wottpal I'm running into the same issue, curious if you've found any solutions..

<!-- gh-comment-id:2957235615 --> @dkokotov commented on GitHub (Jun 9, 2025): @wottpal I'm running into the same issue, curious if you've found any solutions..
Author
Owner

@dkokotov commented on GitHub (Jun 10, 2025):

After some digging, I think the culprit is the catch in the MCP plugin (https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/mcp/index.ts#L142):

const response = await authorizeMCPOAuth(ctx, opts).catch((e) => {
	if (e instanceof APIError) {
		if (e.statusCode === 302) {
			return ctx.json({
				redirect: true,
				//@ts-expect-error
				url: e.headers.get("location"),
			});
		}
	}
	throw e;
});

Notably this catch does not exist in the equivalent code in the OIDC provider. If I remove it, then the redirect behavior is as expected.

@Bekacru I wonder if you could comment on
a) why this catch was added to the MCP plugin
b) more broadly, how is the MCP plugin different than just using generic OIDC plugin with appropriate config, eg what necessitates a separate MCP plugin (or why MCP plugin is not just a wrapper around OIDC plugin with predefined configuration as needed)

<!-- gh-comment-id:2957348880 --> @dkokotov commented on GitHub (Jun 10, 2025): After some digging, I think the culprit is the catch in the MCP plugin (https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/mcp/index.ts#L142): ``` const response = await authorizeMCPOAuth(ctx, opts).catch((e) => { if (e instanceof APIError) { if (e.statusCode === 302) { return ctx.json({ redirect: true, //@ts-expect-error url: e.headers.get("location"), }); } } throw e; }); ``` Notably this catch does not exist in the equivalent code in the OIDC provider. If I remove it, then the redirect behavior is as expected. @Bekacru I wonder if you could comment on a) why this catch was added to the MCP plugin b) more broadly, how is the MCP plugin different than just using generic OIDC plugin with appropriate config, eg what necessitates a separate MCP plugin (or why MCP plugin is not just a wrapper around OIDC plugin with predefined configuration as needed)
Author
Owner

@K-Mistele commented on GitHub (Aug 6, 2025):

any updates on this?

Possibly an update on this since next.js handles redirects by throwing an exception? https://nextjs.org/docs/app/api-reference/functions/redirect

<!-- gh-comment-id:3157015048 --> @K-Mistele commented on GitHub (Aug 6, 2025): any updates on this? Possibly an update on this since next.js handles redirects by throwing an exception? https://nextjs.org/docs/app/api-reference/functions/redirect
Author
Owner

@K-Mistele commented on GitHub (Aug 6, 2025):

For what it's worth here's my monkey patch, mileage may vary depending on your routing config:

/src/app/mcp-oidc/auth/[[..all]]/route.ts:

import { auth } from '@/lib/auth/mcp/auth'
import { cloneResponse } from '@/lib/utils'
import { toNextJsHandler } from 'better-auth/next-js'
import { NextResponse } from 'next/server'

const betterAuthHandler = toNextJsHandler(auth.handler)

const patchedBetterAuthHandler = async (req: Request) => {
    const url = new URL(req.url)

    const shouldPatchResponse = url.pathname.startsWith('/mcp-oidc/auth/callback')
    console.log('shouldPatchResponse', shouldPatchResponse)

    let response: Response
    if (req.method === 'GET') response = await betterAuthHandler.GET(req)
    else if (req.method === 'POST') response = await betterAuthHandler.POST(req)
    else response = new Response('Method Not Allowed', { status: 405 })

    // This is super annoying but is due to a bug in better-auth that we have to patch
    if (shouldPatchResponse && response.body) {
        const { response: clonedResponse, text } = cloneResponse(response)
        const responseText = await text
        try {
            const json = JSON.parse(responseText)
            if ('url' in json && 'redirect' in json) {
                // NOTE we should return the redirect to the URL
                console.log('should redirect to ', json.url)
                return NextResponse.redirect(json.url)
            }
        } catch (e) {
            console.error('error parsing response text', e)
        }
        return clonedResponse
    }

    return response
}

export const GET = patchedBetterAuthHandler
export const POST = patchedBetterAuthHandler

for the average config you probably want to remove the mcp-oidc segment of the path; I have a weird dual-better-auth-tenant situation where I have one better-auth config for dashboard users and a separate one for MCP server users

<!-- gh-comment-id:3157288463 --> @K-Mistele commented on GitHub (Aug 6, 2025): For what it's worth here's my monkey patch, mileage may vary depending on your routing config: `/src/app/mcp-oidc/auth/[[..all]]/route.ts`: ```typescript import { auth } from '@/lib/auth/mcp/auth' import { cloneResponse } from '@/lib/utils' import { toNextJsHandler } from 'better-auth/next-js' import { NextResponse } from 'next/server' const betterAuthHandler = toNextJsHandler(auth.handler) const patchedBetterAuthHandler = async (req: Request) => { const url = new URL(req.url) const shouldPatchResponse = url.pathname.startsWith('/mcp-oidc/auth/callback') console.log('shouldPatchResponse', shouldPatchResponse) let response: Response if (req.method === 'GET') response = await betterAuthHandler.GET(req) else if (req.method === 'POST') response = await betterAuthHandler.POST(req) else response = new Response('Method Not Allowed', { status: 405 }) // This is super annoying but is due to a bug in better-auth that we have to patch if (shouldPatchResponse && response.body) { const { response: clonedResponse, text } = cloneResponse(response) const responseText = await text try { const json = JSON.parse(responseText) if ('url' in json && 'redirect' in json) { // NOTE we should return the redirect to the URL console.log('should redirect to ', json.url) return NextResponse.redirect(json.url) } } catch (e) { console.error('error parsing response text', e) } return clonedResponse } return response } export const GET = patchedBetterAuthHandler export const POST = patchedBetterAuthHandler ``` for the average config you probably want to remove the `mcp-oidc` segment of the path; I have a weird dual-better-auth-tenant situation where I have one better-auth config for dashboard users and a separate one for MCP server users
Author
Owner

@ping-maxwell commented on GitHub (Oct 2, 2025):

Can someone confirm if this issue still persists?

<!-- gh-comment-id:3359383987 --> @ping-maxwell commented on GitHub (Oct 2, 2025): Can someone confirm if this issue still persists?
Author
Owner

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

Hi all, we released the new OAuth Provider Plugin which should fix redirection issues. Feel free to let us know how it works :)

<!-- gh-comment-id:3688618504 --> @dvanmali commented on GitHub (Dec 24, 2025): Hi all, we released the new [OAuth Provider Plugin](https://www.better-auth.com/docs/plugins/oauth-provider) which should fix redirection issues. Feel free to let us know how it works :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9395