{"record":{"id":"fbc1684ff125b563","repo":"mastra-ai/mastra","slug":"failed-to-start-oauth-callback-server-ports-fir","errorCode":null,"errorMessage":"Failed to start OAuth callback server: ports ${firstPort}-${lastPort} are all in use","messagePattern":"Failed to start OAuth callback server: ports (.+?)-(.+?) are all in use","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mcp/src/client/oauth-callback-server.ts","lineNumber":299,"sourceCode":"  const hostname = candidates[0]!.hostname.replace(/^\\[|\\]$/g, '');\n\n  let boundUrl: URL | undefined;\n  let boundPort: number | undefined;\n  for (const candidate of candidates) {\n    const candidatePort = Number(candidate.port);\n    const error = await listen(server, candidatePort, hostname);\n    if (!error) {\n      boundUrl = candidate;\n      // Preserve the port we actually listened on. Reading URL.port back would\n      // return '' for the default 80/443, losing the effective port.\n      boundPort = candidatePort;\n      break;\n    }\n  }\n  if (!boundUrl || boundPort === undefined) {\n    const firstPort = Number(candidates[0]!.port);\n    const lastPort = Number(candidates[candidates.length - 1]!.port);\n    throw new Error(`Failed to start OAuth callback server: ports ${firstPort}-${lastPort} are all in use`);\n  }\n\n  // The bind-time 'error' listener is removed once 'listening' fires, so after\n  // this point the server has no 'error' handler. An emitted 'error' (e.g. a\n  // post-bind socket failure) with no listener throws and would crash the host\n  // process. Keep a persistent listener that settles the flow with the error\n  // instead of letting it become an uncaught exception.\n  server.on('error', error => {\n    settle({ error: error instanceof Error ? error : new Error(String(error)) });\n  });\n\n  return {\n    url: boundUrl,\n    port: boundPort,\n\n    waitForCode({ timeoutMs = DEFAULT_CALLBACK_TIMEOUT_MS } = {}) {\n      let timer: NodeJS.Timeout | undefined;\n      const timeout = new Promise<never>((_, reject) => {","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/mcp/src/client/oauth-callback-server.ts#L281-L317","documentation":"Error thrown by createOAuthCallbackServer after attempting to bind each candidate loopback port; if none could be bound, it reports the full candidate port range as in use. The OAuth flow needs a local HTTP server to receive the authorization redirect, so this is fatal to the flow.","triggerScenarios":"Starting an OAuth authorization flow when every port in the configured candidate port range is already bound by other processes (or blocked by firewall/permissions).","commonSituations":"Stale dev servers or previous crashed runs still holding the callback port; multiple concurrent OAuth flows for different servers competing for the same range; containers/CI where the port range collides with other services.","solutions":["Identify and stop the processes occupying the ports (lsof -i :PORT / netstat), or wait for them to exit.","Configure a different or wider callback port range in the provider/redirect URL.","Ensure previous OAuth flows are cancelled/completed so their callback servers are released.","In CI/containers, reserve unique port ranges per test or worker to avoid cross-process collisions."],"exampleFix":"// before\nnew MCPOAuthClientProvider({ redirectUrl: 'http://localhost:3456/callback' }) // 3456 busy\n// after\nconst freePort = await getFreePort(4000, 4100);\nnew MCPOAuthClientProvider({ redirectUrl: `http://localhost:${freePort}/callback` })","handlingStrategy":"retry","validationCode":"import net from 'node:net';\nfunction isPortFree(port: number): Promise<boolean> {\n  return new Promise(res => {\n    const s = net.createServer();\n    s.once('error', () => res(false));\n    s.once('listening', () => s.close(() => res(true)));\n    s.listen(port, 'localhost');\n  });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await startOAuthFlow();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('are all in use')) {\n    await releaseStaleServers(); // cancel previous flows / kill stale listeners\n    await startOAuthFlow(); // retry once\n  } else throw e;\n}","preventionTips":["Configure a wide candidate port range for the callback server.","Always cancel/complete prior OAuth flows so their callback servers are released.","In CI, assign unique port ranges per worker.","Check for stale processes holding the ports before starting flows."],"tags":["mcp","oauth","port-in-use","network","callback-server"],"backgroundTag":"port-in-use","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}