Skip to content

Commit c91ff7f

Browse files
committed
feat: add mfa functionalities and sensitive data parameter
1 parent 3618120 commit c91ff7f

File tree

6 files changed

+4090
-698
lines changed

6 files changed

+4090
-698
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ classifiers = [
1414
]
1515
dependencies = [
1616
"asyncio>=3.4.3",
17-
"browser-use>=0.1.40",
17+
"browser-use>=0.2.5",
1818
"click>=8.1.8",
1919
"httpx>=0.28.1",
2020
"langchain-openai>=0.3.1",
@@ -26,6 +26,7 @@ dependencies = [
2626
"starlette",
2727
"uvicorn",
2828
"playwright>=1.50.0",
29+
"pyotp>=2.9.0",
2930
]
3031

3132
[project.optional-dependencies]

server/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
CONFIG,
99
Server,
1010
cleanup_old_tasks,
11-
create_browser_context_for_task,
11+
create_browser_session_for_task,
1212
create_mcp_server,
1313
init_configuration,
1414
main,
@@ -19,7 +19,7 @@
1919
__all__ = [
2020
"Server",
2121
"main",
22-
"create_browser_context_for_task",
22+
"create_browser_session_for_task",
2323
"run_browser_task_async",
2424
"cleanup_old_tasks",
2525
"create_mcp_server",

server/server.py

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@
3030
import uvicorn
3131

3232
# Browser-use library imports
33-
from browser_use import Agent
33+
from browser_use import Agent, BrowserSession, BrowserProfile
3434
from browser_use.browser.browser import Browser, BrowserConfig
35-
from browser_use.browser.context import BrowserContext, BrowserContextConfig
3635
from dotenv import load_dotenv
3736
from langchain_core.language_models import BaseLanguageModel
3837

@@ -45,6 +44,7 @@
4544
from pythonjsonlogger import jsonlogger
4645
from starlette.applications import Starlette
4746
from starlette.routing import Mount, Route
47+
from utils.twofa import controller
4848

4949
# Configure logging
5050
logger = logging.getLogger()
@@ -138,16 +138,16 @@ def init_configuration() -> Dict[str, Any]:
138138
task_store: Dict[str, Dict[str, Any]] = {}
139139

140140

141-
async def create_browser_context_for_task(
141+
async def create_browser_session_for_task(
142142
chrome_path: Optional[str] = None,
143143
window_width: int = CONFIG["DEFAULT_WINDOW_WIDTH"],
144144
window_height: int = CONFIG["DEFAULT_WINDOW_HEIGHT"],
145145
locale: str = CONFIG["DEFAULT_LOCALE"],
146-
) -> Tuple[Browser, BrowserContext]:
146+
) -> BrowserSession:
147147
"""
148-
Create a fresh browser and context for a task.
148+
Create a fresh browser and session for a task.
149149
150-
This function creates an isolated browser instance and context
150+
This function creates an isolated browser instance and session
151151
with proper configuration for a single task.
152152
153153
Args:
@@ -157,42 +157,48 @@ async def create_browser_context_for_task(
157157
locale: Browser locale
158158
159159
Returns:
160-
A tuple containing the browser instance and browser context
160+
A tuple containing the browser instance and browser session
161161
162162
Raises:
163-
Exception: If browser or context creation fails
163+
Exception: If browser or session creation fails
164164
"""
165165
try:
166-
# Create browser configuration
167-
browser_config = BrowserConfig(
168-
extra_chromium_args=CONFIG["BROWSER_ARGS"],
169-
)
170-
171-
# Set chrome path if provided
172-
if chrome_path:
173-
browser_config.chrome_instance_path = chrome_path
174-
175-
# Create browser instance
176-
browser = Browser(config=browser_config)
177-
178-
# Create context configuration
179-
context_config = BrowserContextConfig(
166+
# Create session configuration
167+
browser_profile = BrowserProfile(
180168
wait_for_network_idle_page_load_time=0.6,
181169
maximum_wait_page_load_time=1.2,
182170
minimum_wait_page_load_time=0.2,
183-
browser_window_size={"width": window_width, "height": window_height},
171+
window_size={"width": window_width, "height": window_height},
172+
viewport={"width": window_width, "height": window_height},
184173
locale=locale,
185174
user_agent=CONFIG["DEFAULT_USER_AGENT"],
186175
highlight_elements=True,
187176
viewport_expansion=0,
177+
chromium_sandbox=False,
178+
)
179+
180+
181+
# Create session with the browser
182+
session = BrowserSession(
183+
browser_profile=browser_profile,
184+
headless=True,
185+
browser_args=[
186+
'--no-sandbox',
187+
'--disable-setuid-sandbox',
188+
'--no-first-run',
189+
'--disable-default-apps',
190+
'--disable-extensions-except=',
191+
'--disable-background-timer-throttling',
192+
'--disable-backgrounding-occluded-windows',
193+
'--disable-renderer-backgrounding',
194+
'--disable-features=TranslateUI',
195+
'--disable-ipc-flooding-protection'
196+
]
188197
)
189198

190-
# Create context with the browser
191-
context = BrowserContext(browser=browser, config=context_config)
192-
193-
return browser, context
199+
return session
194200
except Exception as e:
195-
logger.error(f"Error creating browser context: {str(e)}")
201+
logger.error(f"Error creating browser session: {str(e)}")
196202
raise
197203

198204

@@ -201,6 +207,8 @@ async def run_browser_task_async(
201207
url: str,
202208
action: str,
203209
llm: BaseLanguageModel,
210+
sensitive_data: Dict[str, str] | None = None,
211+
204212
window_width: int = CONFIG["DEFAULT_WINDOW_WIDTH"],
205213
window_height: int = CONFIG["DEFAULT_WINDOW_HEIGHT"],
206214
locale: str = CONFIG["DEFAULT_LOCALE"],
@@ -218,13 +226,13 @@ async def run_browser_task_async(
218226
task_id: Unique identifier for the task
219227
url: URL to navigate to
220228
action: Action to perform after navigation
229+
sensitive_data: Sensitive data to use for the task
221230
llm: Language model to use for browser agent
222231
window_width: Browser window width
223232
window_height: Browser window height
224233
locale: Browser locale
225234
"""
226-
browser = None
227-
context = None
235+
session = None
228236

229237
try:
230238
# Update task status to running
@@ -278,19 +286,33 @@ async def done_callback(history: Any) -> None:
278286
# Get Chrome path from environment if available
279287
chrome_path = os.environ.get("CHROME_PATH")
280288

281-
# Create a fresh browser and context for this task
282-
browser, context = await create_browser_context_for_task(
289+
# Create a fresh browser and session for this task
290+
session = await create_browser_session_for_task(
283291
chrome_path=chrome_path,
284292
window_width=window_width,
285293
window_height=window_height,
286294
locale=locale,
287295
)
288296

289-
# Create agent with the fresh context
297+
action = f"""
298+
{action}\n\n\n
299+
Considerations:
300+
- NEVER hallucinate login credentials.
301+
- ALWAYS use the get_otp_2fa action to retrieve the 2FA code if needed.
302+
- NEVER skip the 2FA step if the page requires it.
303+
- NEVER extract the code from the page.
304+
- NEVER use a code that is not generated by the get_otp_2fa action.
305+
- NEVER hallucinate the 2FA code, always use the get_otp_2fa action to get it.
306+
"""
307+
308+
# Create agent with the fresh session
290309
agent = Agent(
291310
task=f"First, navigate to {url}. Then, {action}",
292311
llm=llm,
293-
browser_context=context,
312+
sensitive_data=sensitive_data,
313+
use_vision=False,
314+
browser_session=session,
315+
controller=controller,
294316
register_new_step_callback=step_callback,
295317
register_done_callback=done_callback,
296318
)
@@ -349,10 +371,8 @@ async def done_callback(history: Any) -> None:
349371
finally:
350372
# Clean up browser resources
351373
try:
352-
if context:
353-
await context.close()
354-
if browser:
355-
await browser.close()
374+
if session:
375+
await session.close()
356376
logger.info(f"Browser resources for task {task_id} cleaned up")
357377
except Exception as e:
358378
logger.error(
@@ -467,6 +487,7 @@ async def call_tool(
467487
url=arguments["url"],
468488
action=arguments["action"],
469489
llm=llm,
490+
sensitive_data=arguments.get("sensitive_data", None),
470491
window_width=window_width,
471492
window_height=window_height,
472493
locale=locale,
@@ -608,6 +629,13 @@ async def list_tools() -> list[types.Tool]:
608629
"type": "string",
609630
"description": "Action to perform in the browser",
610631
},
632+
"sensitive_data": {
633+
"type": "object",
634+
"description": "Dictionary of sensitive data to use for the task (e.g. credentials)",
635+
"additionalProperties": {
636+
"type": "string"
637+
}
638+
},
611639
},
612640
},
613641
),
@@ -643,6 +671,13 @@ async def list_tools() -> list[types.Tool]:
643671
"type": "string",
644672
"description": "Action to perform in the browser",
645673
},
674+
"sensitive_data": {
675+
"type": "object",
676+
"description": "Dictionary of sensitive data to use for the task (e.g. credentials)",
677+
"additionalProperties": {
678+
"type": "string"
679+
}
680+
},
646681
},
647682
},
648683
),
@@ -656,7 +691,14 @@ async def list_tools() -> list[types.Tool]:
656691
"task_id": {
657692
"type": "string",
658693
"description": "ID of the task to get results for",
659-
}
694+
},
695+
"sensitive_data": {
696+
"type": "object",
697+
"description": "Dictionary of sensitive data to use for the task (e.g. credentials)",
698+
"additionalProperties": {
699+
"type": "string"
700+
}
701+
},
660702
},
661703
},
662704
),
@@ -772,7 +814,7 @@ def main(
772814
Run the browser-use MCP server.
773815
774816
This function initializes the MCP server and runs it with the SSE transport.
775-
Each browser task will create its own isolated browser context.
817+
Each browser task will create its own isolated browser session.
776818
777819
The server can run in two modes:
778820
1. Direct SSE mode (default): Just runs the SSE server

server/utils/twofa.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
import sys
3+
4+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
5+
6+
from dotenv import load_dotenv
7+
8+
load_dotenv()
9+
10+
import pyotp
11+
12+
from browser_use import ActionResult, Controller
13+
14+
15+
controller = Controller()
16+
17+
18+
@controller.registry.action('Get 2FA code from when OTP is required')
19+
async def get_otp_2fa() -> ActionResult:
20+
"""
21+
Custom action to retrieve 2FA/MFA code from OTP secret key using pyotp.
22+
The OTP secret key should be set in the environment variable OTP_SECRET_KEY.
23+
"""
24+
secret_key = os.environ.get('OTP_SECRET_KEY', None)
25+
if not secret_key:
26+
raise ValueError('OTP_SECRET_KEY environment variable is not set')
27+
28+
totp = pyotp.TOTP(secret_key)
29+
code = totp.now()
30+
return ActionResult(extracted_content=code)

src/browser_use_mcp_server/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
CONFIG,
99
Server,
1010
cleanup_old_tasks,
11-
create_browser_context_for_task,
11+
create_browser_session_for_task,
1212
create_mcp_server,
1313
init_configuration,
1414
main,
@@ -20,7 +20,7 @@
2020
__all__ = [
2121
"Server",
2222
"main",
23-
"create_browser_context_for_task",
23+
"create_browser_session_for_task",
2424
"run_browser_task_async",
2525
"cleanup_old_tasks",
2626
"create_mcp_server",

0 commit comments

Comments
 (0)
close