Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions examples/inspector/src/components/McpServers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ import { useMcp, type Tool } from 'use-mcp/react'
import { Info, X, ChevronRight, ChevronDown } from 'lucide-react'

// MCP Connection wrapper that only renders when active
function McpConnection({ serverUrl, onConnectionUpdate }: { serverUrl: string; onConnectionUpdate: (data: any) => void }) {
function McpConnection({
serverUrl,
transportType,
onConnectionUpdate,
}: {
serverUrl: string
transportType: 'auto' | 'http' | 'sse'
onConnectionUpdate: (data: any) => void
}) {
// Use the MCP hook with the server URL
const connection = useMcp({
url: serverUrl,
debug: true,
autoRetry: false,
popupFeatures: 'width=500,height=600,resizable=yes,scrollbars=yes',
transportType: 'auto',
transportType,
})

// Update parent component with connection data
Expand All @@ -26,6 +34,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
const [serverUrl, setServerUrl] = useState(() => {
return sessionStorage.getItem('mcpServerUrl') || ''
})
const [transportType, setTransportType] = useState<'auto' | 'http' | 'sse'>(() => {
return (sessionStorage.getItem('mcpTransportType') as 'auto' | 'http' | 'sse') || 'auto'
})
const [isActive, setIsActive] = useState(false)

const [connectionData, setConnectionData] = useState<any>({
Expand Down Expand Up @@ -293,6 +304,20 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
}}
disabled={isActive && state !== 'failed'}
/>
<select
className="p-2 border border-gray-200 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-300"
value={transportType}
onChange={(e) => {
const newValue = e.target.value as 'auto' | 'http' | 'sse'
setTransportType(newValue)
sessionStorage.setItem('mcpTransportType', newValue)
}}
disabled={isActive && state !== 'failed'}
>
<option value="auto">Auto</option>
<option value="http">HTTP</option>
<option value="sse">SSE</option>
</select>

{state === 'ready' || (isActive && state !== 'not-connected' && state !== 'failed') ? (
<button
Expand Down Expand Up @@ -477,7 +502,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
</div>

{/* Only render the actual MCP connection when active */}
{isActive && <McpConnection serverUrl={serverUrl} onConnectionUpdate={setConnectionData} />}
{isActive && <McpConnection serverUrl={serverUrl} transportType={transportType} onConnectionUpdate={setConnectionData} />}
</section>
)
}
4 changes: 2 additions & 2 deletions examples/inspector/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
build: {
// @ts-ignore
minify: !process.env.NO_MINIFY,
// @ts-expect-error I don't want to install @types/node for this one line
minify: process.env.NO_MINIFY !== 'true',
},
})
17 changes: 13 additions & 4 deletions src/react/useMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,10 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
}

// Handle other connection errors
// Use stable failConnection
// For HTTP transport, consider fallback only for specific error types
// "Not connected" errors should still be treated as failures, not fallback triggers
failConnection(`Failed to connect via ${transportType.toUpperCase()}: ${errorMessage}`, errorInstance)
return 'fallback' // If our logic above is wrong, we'd prevent HTTPs servers from ever working. So always fall back as a last resort.
return 'failed'
}
} // End of tryConnectWithTransport helper

Expand All @@ -392,10 +393,18 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
// Auto mode - try HTTP first, fallback to SSE
addLog('debug', 'Using auto transport mode (HTTP with SSE fallback)')
const httpResult = await tryConnectWithTransport('http')
// 2. Try SSE only if HTTP requested fallback and we haven't failed/redirected
if (httpResult === 'fallback' && isMountedRef.current && stateRef.current !== 'failed' && stateRef.current !== 'authenticating') {

// Try SSE only if HTTP requested fallback and we haven't redirected for auth
// Allow fallback even if state is 'failed' from a previous HTTP attempt in auto mode
if (httpResult === 'fallback' && isMountedRef.current && stateRef.current !== 'authenticating') {
addLog('info', 'HTTP failed, attempting SSE fallback...')
const sseResult = await tryConnectWithTransport('sse')
finalStatus = sseResult // Use SSE result as final status

// If SSE also failed, we need to properly fail the connection since HTTP didn't call failConnection
if (sseResult === 'failed' && isMountedRef.current) {
// SSE failure already called failConnection, so we don't need to do anything else
}
} else {
finalStatus = httpResult // Use HTTP result if no fallback was needed/possible
}
Expand Down
54 changes: 45 additions & 9 deletions test/integration/mcp-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,22 @@ function getMCPServers() {
url: `http://localhost:${state.cfAgentsPort}/mcp`,
expectedTools: 1, // Minimum expected tools count
},
{
name: 'cf-agents-sse',
url: `http://localhost:${state.cfAgentsPort}/sse`,
expectedTools: 1, // Minimum expected tools count
},
]
} catch (error) {
throw new Error(`Test environment not properly initialized: ${error}`)
}
}

async function connectToMCPServer(page: Page, serverUrl: string): Promise<{ success: boolean; tools: string[]; debugLog: string }> {
async function connectToMCPServer(
page: Page,
serverUrl: string,
transportType: 'auto' | 'http' | 'sse' = 'auto',
): Promise<{ success: boolean; tools: string[]; debugLog: string }> {
// Navigate to the inspector
const stateData = readFileSync(testStateFile, 'utf-8')
const state = JSON.parse(stateData)
Expand All @@ -58,6 +67,10 @@ async function connectToMCPServer(page: Page, serverUrl: string): Promise<{ succ
const urlInput = page.locator('input[placeholder="Enter MCP server URL"]')
await urlInput.fill(serverUrl)

// Set transport type
const transportSelect = page.locator('select')
await transportSelect.selectOption(transportType)

// Click connect button
const connectButton = page.locator('button:has-text("Connect")')
await connectButton.click()
Expand Down Expand Up @@ -202,13 +215,35 @@ describe('MCP Connection Integration Tests', () => {
}
})

test('should connect to all MCP servers and retrieve tools', async () => {
const servers = getMCPServers()
const testScenarios = [
// Working examples with auto transport (should pass)
{ serverName: 'hono-mcp', transportType: 'auto' as const },
{ serverName: 'cf-agents', transportType: 'auto' as const },

// SSE endpoint with SSE transport (should pass)
{ serverName: 'cf-agents-sse', transportType: 'sse' as const },

// Additional test cases for HTTP transport
{ serverName: 'hono-mcp', transportType: 'http' as const },
{ serverName: 'cf-agents', transportType: 'http' as const },

for (const server of servers) {
console.log(`\n🔗 Testing connection to ${server.name} at ${server.url}`)
// Failing case: SSE endpoint with auto transport (should fail)
{ serverName: 'cf-agents-sse', transportType: 'auto' as const },
]

const result = await connectToMCPServer(page, server.url)
test.each(testScenarios)(
'should connect to $serverName with $transportType transport (expect: $shouldPass)',
async ({ serverName, transportType }) => {
const servers = getMCPServers()
const server = servers.find((s) => s.name === serverName)

if (!server) {
throw new Error(`Server ${serverName} not found. Available servers: ${servers.map((s) => s.name).join(', ')}`)
}

console.log(`\n🔗 Testing connection to ${server.name} at ${server.url} with ${transportType} transport`)

const result = await connectToMCPServer(page, server.url, transportType)

if (result.success) {
console.log(`✅ Successfully connected to ${server.name}`)
Expand All @@ -228,8 +263,9 @@ describe('MCP Connection Integration Tests', () => {
}

// Fail the test with detailed information
throw new Error(`Failed to connect to ${server.name}. Debug log: ${result.debugLog}`)
throw new Error(`Expected to connect to ${server.name} with ${transportType} transport but failed. Debug log: ${result.debugLog}`)
}
}
}, 45000)
},
45000,
)
})
5 changes: 3 additions & 2 deletions test/setup/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,13 @@ function findAvailablePortFromBase(basePort: number): Promise<number> {
})
}

function runCommand(command: string, args: string[], cwd: string): Promise<void> {
function runCommand(command: string, args: string[], cwd: string, env?: Record<string, string>): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd,
stdio: 'inherit',
shell: true,
env: env ? { ...process.env, ...env } : process.env,
})

child.on('close', (code) => {
Expand Down Expand Up @@ -209,7 +210,7 @@ export default async function globalSetup() {
// Step 2: Build inspector example
console.log('📦 Building inspector example...')
const inspectorDir = join(rootDir, 'examples/inspector')
await runCommand('pnpm', ['build'], inspectorDir)
await runCommand('pnpm', ['build'], inspectorDir, { NO_MINIFY: 'true' })

// Step 3: Find available port and start hono-mcp server
console.log('🔍 Finding available port starting from 9901...')
Expand Down
Loading