(name: string, config: McpServerConfig, authIntercept?: AuthIntercept)
| 70 | * `authIntercept` lets the caller surface the OAuth URL in the TUI and |
| 71 | * provide the auth code (from the callback or a manual paste). */ |
| 72 | export async function connectMcpServer(name: string, config: McpServerConfig, authIntercept?: AuthIntercept): Promise<McpConnection> { |
| 73 | const { transport, authenticate } = buildTransport(name, config, authIntercept) |
| 74 | const client = new Client(CLIENT_INFO, { capabilities: {} }) |
| 75 | |
| 76 | // For OAuth servers, run the auth flow (browser redirect or M2M) before |
| 77 | // connecting. This may open a browser and block until the user authorizes. |
| 78 | if (authenticate) { |
| 79 | await withTimeout(authenticate(), CONNECT_TIMEOUT_MS, `MCP server "${name}" auth timed out`) |
| 80 | } |
| 81 | |
| 82 | // Wrap connect in a timeout so a hung server doesn't block startup. |
| 83 | await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `MCP server "${name}" timed out`) |
| 84 | |
| 85 | const tools = await listServerTools(name, client) |
| 86 | return { |
| 87 | client, |
| 88 | tools, |
| 89 | close: async () => { |
| 90 | try { |
| 91 | await transport.close() |
| 92 | } catch { |
| 93 | // best-effort |
| 94 | } |
| 95 | try { |
| 96 | await client.close() |
| 97 | } catch { |
| 98 | // best-effort |
| 99 | } |
| 100 | }, |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /** Enumerate tools from a connected client, namespaced as mcp__<server>__<tool>. */ |
| 105 | export async function listServerTools(server: string, client: Client): Promise<McpTool[]> { |
no test coverage detected