Skip to content

Commit 37da441

Browse files
waleedlatif1claude
andcommitted
fix(mcp): make OAuth probe work via JSON add modal and preserve HTTP status
Two changes that together let spec-compliant OAuth servers (Semrush, Linear, Notion, Atlassian) connect via either modal path: 1. client.ts: when the SDK throws StreamableHTTPError, prefix the wrapped error message with `HTTP <code>:` so the status code survives McpConnectionError wrapping. Without this the 401 status is invisible downstream — the message read just "Streamable HTTP error: Error POSTing to endpoint:" with no body for empty 401s. 2. form modal: extract isAuthRequiredError helper and apply it to BOTH handleSubmitForm AND handleSubmitJson. Previously only the form path swallowed auth-shaped test-connection failures and proceeded to the create flow (where the probe runs and the OAuth dance starts). The JSON path treated any failure as fatal, blocking Semrush et al. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9fbbbff commit 37da441

2 files changed

Lines changed: 31 additions & 16 deletions

File tree

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ export interface McpServerFormModalProps {
6767

6868
const ENV_VAR_PATTERN = /\{\{[^}]+\}\}/
6969

70+
/**
71+
* Treats a failed test-connection as a soft failure when the response looks like
72+
* an OAuth/auth challenge — the create flow can then run the probe and kick off
73+
* the OAuth handshake. Without this, OAuth-protected servers like Semrush get
74+
* rejected at the modal before the probe ever runs.
75+
*/
76+
function isAuthRequiredError(errorMessage: string | undefined): boolean {
77+
const text = (errorMessage || '').toLowerCase()
78+
return (
79+
/\b401\b/.test(text) ||
80+
text.includes('unauthorized') ||
81+
text.includes('oauth') ||
82+
text.includes('authentication')
83+
)
84+
}
85+
7086
function hasEnvVarInHostname(url: string): boolean {
7187
const globalPattern = new RegExp(ENV_VAR_PATTERN.source, 'g')
7288
if (url.trim().replace(globalPattern, '').trim() === '') return true
@@ -517,19 +533,11 @@ export function McpServerFormModal({
517533
workspaceId,
518534
})
519535

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-
}
536+
if (!connectionResult.success && !isAuthRequiredError(connectionResult.error)) {
537+
setSubmitError(
538+
connectionResult.error || 'Connection test failed. Please check the URL and try again.'
539+
)
540+
return
533541
}
534542

535543
await onSubmit({
@@ -587,7 +595,7 @@ export function McpServerFormModal({
587595
workspaceId,
588596
})
589597

590-
if (!connectionResult.success) {
598+
if (!connectionResult.success && !isAuthRequiredError(connectionResult.error)) {
591599
setSubmitError(
592600
connectionResult.error || 'Connection test failed. Please check the URL and try again.'
593601
)

apps/sim/lib/mcp/client.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
1212
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
13-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
13+
import {
14+
StreamableHTTPClientTransport,
15+
StreamableHTTPError,
16+
} from '@modelcontextprotocol/sdk/client/streamableHttp.js'
1417
import {
1518
type ListToolsResult,
1619
type Tool,
@@ -127,7 +130,11 @@ export class McpClient {
127130
this.connectionStatus.lastError = undefined
128131
throw error
129132
}
130-
const errorMessage = getErrorMessage(error, 'Unknown error')
133+
const rawMessage = getErrorMessage(error, 'Unknown error')
134+
const errorMessage =
135+
error instanceof StreamableHTTPError && typeof error.code === 'number'
136+
? `HTTP ${error.code}: ${rawMessage}`
137+
: rawMessage
131138
this.connectionStatus.lastError = errorMessage
132139
logger.error(`Failed to connect to MCP server ${this.config.name}:`, error)
133140
throw new McpConnectionError(errorMessage, this.config.name)

0 commit comments

Comments
 (0)