@@ -52,8 +52,8 @@ class LocalEndpointManager(EndpointManager):
5252
5353 Key behaviors:
5454 - Starts local HTTP server on demand (lazy initialization)
55- - Opens user's browser to authorization URL
56- - BLOCKS in initiate_redirect() until callback is received
55+ - Opens user's browser to authorization URL (configurable)
56+ - BLOCKS in initiate_redirect() until callback is received (configurable)
5757 - Tracks pending flows by OAuth state parameter
5858
5959 Use for: CLI apps, desktop apps, local development.
@@ -63,7 +63,9 @@ def __init__(
6363 self ,
6464 host : str = "localhost" ,
6565 port : int = 0 ,
66- callback_path : str = "/callback"
66+ callback_path : str = "/callback" ,
67+ auto_open_browser : bool = True ,
68+ block_until_callback : bool = True
6769 ):
6870 """
6971 Initialize local endpoint manager.
@@ -72,11 +74,15 @@ def __init__(
7274 host: Host for local server (default: localhost)
7375 port: Port for local server (0 = auto-assign)
7476 callback_path: Path for callback endpoint (default: /callback)
77+ auto_open_browser: Whether to automatically open browser (default: True)
78+ block_until_callback: Whether to block until callback received (default: True)
7579 """
7680 self ._host = host
7781 self ._desired_port = port
7882 self ._actual_port : int | None = None
7983 self ._callback_path = callback_path
84+ self ._auto_open_browser = auto_open_browser
85+ self ._block_until_callback = block_until_callback
8086
8187 self ._server_task : asyncio .Task | None = None
8288 self ._ready_event = asyncio .Event ()
@@ -125,21 +131,22 @@ async def get_redirect_uris(self) -> list[str]:
125131
126132 async def initiate_redirect (self , url : str , metadata : dict [str , Any ]) -> None :
127133 """
128- Open browser and BLOCK until callback is received .
134+ Initiate OAuth redirect (configurable browser opening and blocking) .
129135
130- This is the key behavior for CLI apps: the function blocks until
131- the user completes authorization in their browser.
136+ Behavior depends on configuration:
137+ - auto_open_browser=True: Opens browser automatically
138+ - auto_open_browser=False: Logs URL for manual opening
139+ - block_until_callback=True: Blocks until callback received
140+ - block_until_callback=False: Returns immediately
132141
133142 Args:
134143 url: Authorization URL to open in browser
135144 metadata: Flow metadata including server_name
136145
137146 Raises:
138- TimeoutError: If authorization not completed within 300 seconds
147+ TimeoutError: If authorization not completed within 300 seconds (when blocking)
139148 """
140149 server_name = metadata .get ("server_name" , "unknown" )
141- logger .info (f"Opening browser for auth flow (server: { server_name } )" )
142- logger .info ("Please authorize in your browser..." )
143150
144151 state = self ._extract_state_from_url (url )
145152 if not state :
@@ -149,16 +156,25 @@ async def initiate_redirect(self, url: str, metadata: dict[str, Any]) -> None:
149156 if state not in self ._pending_flows :
150157 self ._pending_flows [state ] = asyncio .Event ()
151158
152- webbrowser .open (url )
159+ if self ._auto_open_browser :
160+ logger .info (f"Opening browser for auth flow (server: { server_name } )" )
161+ logger .info ("Please authorize in your browser..." )
162+ webbrowser .open (url )
163+ else :
164+ logger .info (f"Authorization required for { server_name } " )
165+ logger .info (f"Please visit: { url } " )
153166
154- logger .info ("Waiting for authorization to complete..." )
155- try :
156- await asyncio .wait_for (self ._pending_flows [state ].wait (), timeout = 300 )
157- logger .info (f"Authorization completed for { server_name } " )
158- except asyncio .TimeoutError as e :
159- logger .error ("Authorization timed out after 300s" )
160- self ._pending_flows .pop (state , None )
161- raise TimeoutError (f"Authorization timed out for { server_name } " ) from e
167+ if self ._block_until_callback :
168+ logger .info ("Waiting for authorization to complete..." )
169+ try :
170+ await asyncio .wait_for (self ._pending_flows [state ].wait (), timeout = 300 )
171+ logger .info (f"Authorization completed for { server_name } " )
172+ except asyncio .TimeoutError as e :
173+ logger .error ("Authorization timed out after 300s" )
174+ self ._pending_flows .pop (state , None )
175+ raise TimeoutError (f"Authorization timed out for { server_name } " ) from e
176+ else :
177+ logger .debug (f"Non-blocking mode: returning immediately for { server_name } " )
162178
163179 async def shutdown (self ) -> None :
164180 """
0 commit comments