diff --git a/examples/chat-ui/src/components/McpServerModal.tsx b/examples/chat-ui/src/components/McpServerModal.tsx index ce98fd7..fea9ac5 100644 --- a/examples/chat-ui/src/components/McpServerModal.tsx +++ b/examples/chat-ui/src/components/McpServerModal.tsx @@ -13,9 +13,11 @@ interface McpServer { function McpConnection({ server, onConnectionUpdate, + transportType, }: { server: McpServer onConnectionUpdate: (serverId: string, data: any) => void + transportType: 'auto' | 'http' | 'sse' }) { // Use the MCP hook with the server URL const connection = useMcp({ @@ -23,7 +25,7 @@ function McpConnection({ debug: true, autoRetry: false, popupFeatures: 'width=500,height=600,resizable=yes,scrollbars=yes', - httpOnly: true, // DEBUG: Only try HTTP transport, no SSE fallback + transportType, }) // Update parent component with connection data @@ -64,6 +66,22 @@ const McpServerModal: React.FC = ({ const stored = localStorage.getItem('mcpServerToolCounts') return stored ? JSON.parse(stored) : {} }) + const [transportType, setTransportType] = useState<'auto' | 'http' | 'sse'>(() => { + const stored = localStorage.getItem('mcpTransportType') + return (stored as 'auto' | 'http' | 'sse') || 'auto' + }) + + // Helper to cycle through transport types + const cycleTransportType = () => { + setTransportType(current => { + switch (current) { + case 'auto': return 'http' + case 'http': return 'sse' + case 'sse': return 'auto' + default: return 'auto' + } + }) + } // const logRef = useRef(null) // Removed for now as debug logs not implemented in multi-server version // Save servers to localStorage whenever they change @@ -76,6 +94,11 @@ const McpServerModal: React.FC = ({ localStorage.setItem('mcpServerToolCounts', JSON.stringify(serverToolCounts)) }, [serverToolCounts]) + // Save transport type to localStorage whenever it changes + useEffect(() => { + localStorage.setItem('mcpTransportType', transportType) + }, [transportType]) + useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden' @@ -296,6 +319,11 @@ const McpServerModal: React.FC = ({
{getStatusBadge(server.enabled ? state : 'disabled')} + {server.enabled && state === 'ready' && ( + + {transportType.toUpperCase()} + + )} +
= ({ key={server.id} server={server} onConnectionUpdate={handleConnectionUpdate} + transportType={transportType} /> ))} diff --git a/src/react/types.ts b/src/react/types.ts index f9e7d4a..5472b1f 100644 --- a/src/react/types.ts +++ b/src/react/types.ts @@ -26,8 +26,8 @@ export type UseMcpOptions = { autoReconnect?: boolean | number /** Popup window features string (dimensions and behavior) for OAuth */ popupFeatures?: string - /** Only try HTTP transport, don't fallback to SSE (useful for debugging) */ - httpOnly?: boolean + /** Transport type preference: 'auto' (HTTP with SSE fallback), 'http' (HTTP only), 'sse' (SSE only) */ + transportType?: 'auto' | 'http' | 'sse' } export type UseMcpResult = { diff --git a/src/react/useMcp.ts b/src/react/useMcp.ts index ce2cad6..5d224cd 100644 --- a/src/react/useMcp.ts +++ b/src/react/useMcp.ts @@ -30,7 +30,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { debug = false, autoRetry = false, autoReconnect = DEFAULT_RECONNECT_DELAY, - httpOnly = false, + transportType = 'auto', } = options const [state, setState] = useState('discovering') @@ -315,12 +315,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { errorMessage === 'Load failed' /* Safari */ if (transportType === 'http' && (is404 || is405 || isLikelyCors)) { - if (httpOnly) { - addLog('warn', `HTTP transport failed (${isLikelyCors ? 'CORS' : is404 ? '404' : '405'}). httpOnly=true, no fallback.`) - failConnection(`HTTP transport failed: ${isLikelyCors ? 'CORS' : is404 ? '404' : '405'}`, errorInstance) - return 'failed' - } - addLog('warn', `HTTP transport failed (${isLikelyCors ? 'CORS' : is404 ? '404' : '405'}). Will attempt fallback to SSE.`) + addLog('warn', `HTTP transport failed (${isLikelyCors ? 'CORS' : is404 ? '404' : '405'}).`) return 'fallback' // Signal that fallback should be attempted } @@ -361,7 +356,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { if (authTimeoutRef.current) clearTimeout(authTimeoutRef.current) addLog('error', 'Auth flow failed:', sdkAuthError) // Auth failed, but still allow fallback to SSE - if (transportType === 'http' && !httpOnly) { + if (transportType === 'http') { return 'fallback' // Try SSE even after auth failure } else { failConnection( @@ -377,12 +372,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Don't call failConnection here for HTTP transport - let orchestration handle it // so that SSE fallback can still be attempted if (transportType === 'http') { - if (httpOnly) { - addLog('warn', `HTTP transport failed: ${errorMessage}. httpOnly=true, no fallback.`) - failConnection(`HTTP transport failed: ${errorMessage}`, errorInstance) - return 'failed' - } - addLog('warn', `HTTP transport failed: ${errorMessage}. Will attempt fallback to SSE.`) + addLog('warn', `HTTP transport failed: ${errorMessage}.`) return 'fallback' } else { // For SSE transport, we can fail immediately since there's no further fallback @@ -395,15 +385,26 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // --- Orchestrate Connection Attempts --- let finalStatus: 'success' | 'auth_redirect' | 'failed' | 'fallback' = 'failed' // Default to failed - // 1. Try HTTP - const httpResult = await tryConnectWithTransport('http') - - // 2. Try SSE only if HTTP requested fallback and we haven't redirected - if (httpResult === 'fallback' && isMountedRef.current && stateRef.current !== 'authenticating') { - const sseResult = await tryConnectWithTransport('sse') - finalStatus = sseResult // Use SSE result as final status + if (transportType === 'sse') { + // SSE only - skip HTTP entirely + addLog('debug', 'Using SSE-only transport mode') + finalStatus = await tryConnectWithTransport('sse') + } else if (transportType === 'http') { + // HTTP only - no fallback + addLog('debug', 'Using HTTP-only transport mode') + finalStatus = await tryConnectWithTransport('http') } else { - finalStatus = httpResult // Use HTTP result if no fallback was needed/possible + // Auto mode - try HTTP first, fallback to SSE + addLog('debug', 'Using auto transport mode (HTTP with SSE fallback)') + const httpResult = await tryConnectWithTransport('http') + + // Try SSE only if HTTP requested fallback and we haven't redirected + if (httpResult === 'fallback' && isMountedRef.current && stateRef.current !== 'authenticating') { + const sseResult = await tryConnectWithTransport('sse') + finalStatus = sseResult // Use SSE result as final status + } else { + finalStatus = httpResult // Use HTTP result if no fallback was needed/possible + } } // If we still have 'fallback' status, convert to 'failed' since no more transports to try