Skip to content

Commit e27afaa

Browse files
authored
fix(mcp): probe-based OAuth detection in test-connection (#4689)
* fix(mcp): probe-based OAuth detection in test-connection * fix(mcp): guard undefined url in probe and use canonical McpAuthType
1 parent 57b9a2f commit e27afaa

4 files changed

Lines changed: 38 additions & 18 deletions

File tree

apps/sim/app/api/mcp/servers/test-connection/route.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import {
1212
validateMcpServerSsrf,
1313
} from '@/lib/mcp/domain-check'
1414
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
15+
import { detectMcpAuthType } from '@/lib/mcp/oauth'
1516
import { resolveMcpConfigEnvVars } from '@/lib/mcp/resolve-config'
16-
import type { McpTransport } from '@/lib/mcp/types'
17+
import type { McpAuthType, McpTransport } from '@/lib/mcp/types'
1718
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
1819

1920
const logger = createLogger('McpServerTestAPI')
@@ -31,6 +32,8 @@ function isUrlBasedTransport(transport: McpTransport): boolean {
3132
interface TestConnectionResult {
3233
success: boolean
3334
error?: string
35+
authRequired?: boolean
36+
authType?: McpAuthType
3437
serverInfo?: {
3538
name: string
3639
version: string
@@ -163,6 +166,18 @@ export const POST = withRouteHandler(
163166
}
164167

165168
const result: TestConnectionResult = { success: false }
169+
170+
// Skip unauth connect when the server returns an RFC 9728 OAuth challenge.
171+
if (testConfig.url) {
172+
const detectedAuthType = await detectMcpAuthType(testConfig.url)
173+
if (detectedAuthType === 'oauth') {
174+
result.authRequired = true
175+
result.authType = 'oauth'
176+
return createMcpSuccessResponse(result, 200)
177+
}
178+
result.authType = detectedAuthType
179+
}
180+
166181
let client: McpClient | null = null
167182

168183
try {

apps/sim/app/workspace/[workspaceId]/settings/components/mcp/components/mcp-server-form-modal/mcp-server-form-modal.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
Textarea,
1818
} from '@/components/emcn'
1919
import { cn } from '@/lib/core/utils/cn'
20-
import type { McpTransport } from '@/lib/mcp/types'
20+
import type { McpAuthType, McpTransport } from '@/lib/mcp/types'
2121
import {
2222
checkEnvVarTrigger,
2323
EnvVarDropdown,
@@ -52,6 +52,7 @@ export interface McpServerFormConfig {
5252
timeout: number
5353
oauthClientId?: string
5454
oauthClientSecret?: string
55+
authType?: McpAuthType
5556
}
5657

5758
export interface McpServerFormModalProps {
@@ -109,11 +110,12 @@ interface EnvVarDropdownConfig {
109110
}
110111

111112
function getTestButtonLabel(
112-
testResult: { success: boolean; error?: string } | null,
113+
testResult: { success: boolean; error?: string; authRequired?: boolean } | null,
113114
isTestingConnection: boolean
114115
): string {
115116
if (isTestingConnection) return 'Testing...'
116117
if (testResult?.success) return 'Connection success'
118+
if (testResult?.authRequired) return 'Requires OAuth'
117119
if (testResult && !testResult.success) return 'No connection: retry'
118120
return 'Test Connection'
119121
}
@@ -517,19 +519,11 @@ export function McpServerFormModal({
517519
workspaceId,
518520
})
519521

520-
if (!connectionResult.success) {
521-
const errorText = (connectionResult.error || '').toLowerCase()
522-
const looksLikeAuthRequired =
523-
/\b401\b/.test(errorText) ||
524-
errorText.includes('unauthorized') ||
525-
errorText.includes('oauth') ||
526-
errorText.includes('authentication')
527-
if (!looksLikeAuthRequired) {
528-
setSubmitError(
529-
connectionResult.error || 'Connection test failed. Please check the URL and try again.'
530-
)
531-
return
532-
}
522+
if (!connectionResult.success && !connectionResult.authRequired) {
523+
setSubmitError(
524+
connectionResult.error || 'Connection test failed. Please check the URL and try again.'
525+
)
526+
return
533527
}
534528

535529
await onSubmit({
@@ -538,6 +532,7 @@ export function McpServerFormModal({
538532
url: formData.url!,
539533
headers,
540534
timeout: formData.timeout || 30000,
535+
authType: connectionResult.authType,
541536
oauthClientId:
542537
mode === 'edit'
543538
? oauthClientIdChanged
@@ -587,7 +582,7 @@ export function McpServerFormModal({
587582
workspaceId,
588583
})
589584

590-
if (!connectionResult.success) {
585+
if (!connectionResult.success && !connectionResult.authRequired) {
591586
setSubmitError(
592587
connectionResult.error || 'Connection test failed. Please check the URL and try again.'
593588
)
@@ -600,6 +595,7 @@ export function McpServerFormModal({
600595
url: config.url,
601596
headers: config.headers,
602597
timeout: 30000,
598+
authType: connectionResult.authType,
603599
})
604600

605601
onOpenChange(false)

apps/sim/hooks/queries/mcp.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ import {
2222
} from '@/lib/api/contracts/mcp'
2323
import { isLoopbackHostname } from '@/lib/core/utils/urls'
2424
import { sanitizeForHttp, sanitizeHeaders } from '@/lib/mcp/shared'
25-
import type { McpServerStatusConfig, McpTool, McpTransport, StoredMcpTool } from '@/lib/mcp/types'
25+
import type {
26+
McpAuthType,
27+
McpServerStatusConfig,
28+
McpTool,
29+
McpTransport,
30+
StoredMcpTool,
31+
} from '@/lib/mcp/types'
2632
import { workflowMcpServerKeys } from '@/hooks/queries/workflow-mcp-servers'
2733

2834
const logger = createLogger('McpQueries')
@@ -54,6 +60,7 @@ export interface McpServerInput {
5460
enabled: boolean
5561
oauthClientId?: string
5662
oauthClientSecret?: string
63+
authType?: McpAuthType
5764
}
5865

5966
async function fetchMcpServers(workspaceId: string, signal?: AbortSignal): Promise<McpServer[]> {

apps/sim/lib/api/contracts/mcp.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ export const mcpServerTestResultSchema = z.object({
252252
success: z.boolean(),
253253
message: z.string().optional(),
254254
error: z.string().optional(),
255+
authRequired: z.boolean().optional(),
256+
authType: mcpAuthTypeSchema.optional(),
255257
serverInfo: z
256258
.object({
257259
name: z.string(),

0 commit comments

Comments
 (0)