import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest' import { chromium, Browser, Page } from 'playwright' import { readFileSync } from 'fs' import { join, dirname } from 'path' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) const testDir = join(__dirname, '..') const testStateFile = join(testDir, 'node_modules/.cache/use-mcp-tests/test-state.json') // Get MCP servers to test (ports determined at runtime) function getMCPServers() { try { const stateData = readFileSync(testStateFile, 'utf-8') const state = JSON.parse(stateData) if (!state.honoPort) { throw new Error('hono-mcp port not found in test state') } if (!state.cfAgentsPort) { throw new Error('cf-agents port not found in test state') } return [ { name: 'hono-mcp', url: `http://localhost:${state.honoPort}/mcp`, expectedTools: 1, // Minimum expected tools count }, { name: 'cf-agents', url: `http://localhost:${state.cfAgentsPort}/public/mcp`, expectedTools: 1, // Minimum expected tools count }, { name: 'cf-agents-sse', url: `http://localhost:${state.cfAgentsPort}/public/sse`, expectedTools: 1, // Minimum expected tools count }, { name: 'cf-agents-auth', url: `http://localhost:${state.cfAgentsPort}/mcp`, expectedTools: 1, // Minimum expected tools count }, { name: 'cf-agents-auth-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, 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) if (!state.staticPort) { throw new Error('Static server port not available - state: ' + JSON.stringify(state)) } const staticPort = state.staticPort await page.goto(`http://localhost:${staticPort}`) // Wait for the page to load await page.waitForSelector('input[placeholder="Enter MCP server URL"]', { timeout: 10000 }) // Enter the server URL 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() // Wait for connection attempt to complete (max 10 seconds) await page.waitForTimeout(1000) // Initial wait // Check for connection status let attempts = 0 const maxAttempts = 20 // 10 seconds total (500ms * 20) let isConnected = false while (attempts < maxAttempts && !isConnected) { try { // Check if status badge shows "Connected" const statusBadge = page.locator('.px-2.py-1.rounded-full') if ((await statusBadge.count()) > 0) { const statusText = await statusBadge.textContent({ timeout: 500 }) if (statusText?.toLowerCase().includes('connected')) { isConnected = true break } } // Also check if tools count is > 0 const toolsHeader = page.locator('h3:has-text("Available Tools")') if ((await toolsHeader.count()) > 0) { const toolsText = await toolsHeader.textContent() if (toolsText && /\d+/.test(toolsText)) { const toolsCount = parseInt(toolsText.match(/\d+/)?.[0] || '0') if (toolsCount > 0) { isConnected = true break } } } } catch (e) { // Continue waiting } await page.waitForTimeout(500) attempts++ } // Extract available tools const tools: string[] = [] try { // Look for tool cards in the tools container const toolCards = page.locator('.bg-white.rounded.border') const toolCount = await toolCards.count() for (let i = 0; i < toolCount; i++) { const toolNameElement = toolCards.nth(i).locator('h4.font-bold.text-base.text-black') const toolName = await toolNameElement.textContent() if (toolName?.trim()) { tools.push(toolName.trim()) } } } catch (e) { console.warn('Could not extract tools list:', e) } // Extract debug log let debugLog = '' try { const debugContainer = page.locator('.h-32.overflow-y-auto.font-mono.text-xs') if ((await debugContainer.count()) > 0) { debugLog = (await debugContainer.first().textContent()) || '' } } catch (e) { console.warn('Could not extract debug log:', e) } return { success: isConnected, tools, debugLog, } } describe('MCP Connection Integration Tests', () => { let browser: Browser let page: Page beforeAll(async () => { const headless = process.env.HEADLESS === 'true' console.log(`🌐 Launching browser in ${headless ? 'headless' : 'headed'} mode`) browser = await chromium.launch({ headless }) }, 30000) afterAll(async () => { if (browser) { await browser.close() } // Force cleanup before Vitest exits - don't throw errors const state = globalThis.__INTEGRATION_TEST_STATE__ try { if (state?.honoServer && !state.honoServer.killed) { console.log('šŸ”„ Force cleanup before test exit...') state.honoServer.kill('SIGKILL') } } catch (e) { // Ignore errors - process might already be dead } try { if (state?.cfAgentsServer && !state.cfAgentsServer.killed) { console.log('šŸ”„ Force cleanup cf-agents server...') state.cfAgentsServer.kill('SIGKILL') } } catch (e) { // Ignore errors - process might already be dead } try { if (state?.staticServer) { state.staticServer.close() state.staticServer.closeAllConnections?.() } } catch (e) { // Ignore errors } }) beforeEach(async () => { const context = await browser.newContext() page = await context.newPage() // Enable console logging for debugging page.on('console', (msg) => { if (msg.type() === 'error') { console.error(`Browser error: ${msg.text()}`) } }) }) afterEach(async () => { if (page) { await page.close() await page.context().close() } }) const testScenarios = [ // Hono examples (MCP only) { serverName: 'hono-mcp', transportType: 'auto' as const }, { serverName: 'hono-mcp', transportType: 'http' as const }, // Agents, no auth { serverName: 'cf-agents', transportType: 'auto' as const }, { serverName: 'cf-agents', transportType: 'http' as const }, { serverName: 'cf-agents-sse', transportType: 'sse' as const }, { serverName: 'cf-agents-sse', transportType: 'auto' as const }, // Agents, with auth { serverName: 'cf-agents-auth', transportType: 'auto' as const }, { serverName: 'cf-agents-auth', transportType: 'http' as const }, { serverName: 'cf-agents-auth-sse', transportType: 'sse' as const }, { serverName: 'cf-agents-auth-sse', transportType: 'auto' as const }, ] test.each(testScenarios)( 'should connect to $serverName with $transportType transport', 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}`) console.log(`šŸ“‹ Available tools (${result.tools.length}):`) result.tools.forEach((tool, index) => { console.log(` ${index + 1}. ${tool}`) }) // Verify connection success expect(result.success).toBe(true) expect(result.tools.length).toBeGreaterThanOrEqual(server.expectedTools) } else { console.log(`āŒ Failed to connect to ${server.name}`) if (result.debugLog) { console.log(`šŸ› Debug log:`) console.log(result.debugLog) } // Fail the test with detailed information throw new Error(`Expected to connect to ${server.name} with ${transportType} transport but failed. Debug log: ${result.debugLog}`) } }, 45000, ) })