Skip to content

Commit fc33e03

Browse files
waleedlatif1claude
andcommitted
fix(mcp): forward serverId on callback failure; allow loopback http
- hoist serverId from try-block const into outer scope so the catch's htmlClose carries it through to postMessage. Without it, parent's onMessage couldn't clear connectingOauthServers and the UI button stayed stuck on "Connecting…" until popup close. - relax https-only authorization URL check to permit http://localhost, http://127.0.0.1, and http://[::1] per OAuth 2.1 loopback exemption, unblocking local OAuth-protected MCP server development. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent f96119f commit fc33e03

2 files changed

Lines changed: 15 additions & 4 deletions

File tree

apps/sim/app/api/mcp/oauth/callback/route.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
5757
return htmlClose('Missing state or code in callback URL.', false)
5858
}
5959

60+
let serverId: string | undefined
6061
try {
6162
const session = await getSession()
6263
if (!session?.user?.id) {
@@ -67,9 +68,14 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
6768
if (!row) {
6869
return htmlClose('Invalid or expired authorization state.', false)
6970
}
71+
serverId = row.mcpServerId
7072

7173
if (session.user.id !== row.userId) {
72-
return htmlClose('You must be signed in as the same user that initiated the flow.', false)
74+
return htmlClose(
75+
'You must be signed in as the same user that initiated the flow.',
76+
false,
77+
serverId
78+
)
7379
}
7480

7581
const [server] = await db
@@ -78,7 +84,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
7884
.where(and(eq(mcpServers.id, row.mcpServerId), isNull(mcpServers.deletedAt)))
7985
.limit(1)
8086
if (!server || !server.url) {
81-
return htmlClose('Server no longer exists.', false)
87+
return htmlClose('Server no longer exists.', false, serverId)
8288
}
8389

8490
// Burn state before token exchange so a replayed callback cannot reuse it.
@@ -107,6 +113,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
107113
return htmlClose('Connected. You can close this window.', true, server.id)
108114
} catch (error) {
109115
logger.error('MCP OAuth callback failed', error)
110-
return htmlClose('Authorization failed. Please try again.', false)
116+
return htmlClose('Authorization failed. Please try again.', false, serverId)
111117
}
112118
})

apps/sim/hooks/queries/mcp.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,12 @@ export function useStartMcpOauth() {
188188
if (result.status === 'already_authorized') return { status: 'already_authorized' }
189189

190190
const parsedUrl = new URL(result.authorizationUrl)
191-
if (parsedUrl.protocol !== 'https:') {
191+
const isLoopbackHttp =
192+
parsedUrl.protocol === 'http:' &&
193+
(parsedUrl.hostname === 'localhost' ||
194+
parsedUrl.hostname === '127.0.0.1' ||
195+
parsedUrl.hostname === '[::1]')
196+
if (parsedUrl.protocol !== 'https:' && !isLoopbackHttp) {
192197
throw new Error('Authorization URL must use HTTPS')
193198
}
194199
const popup = window.open(

0 commit comments

Comments
 (0)