29
29
from browser_use .browser .context import BrowserContext , BrowserContextConfig
30
30
31
31
# MCP server components
32
- from mcp .server . lowlevel import Server
32
+ from mcp .server import Server
33
33
import mcp .types as types
34
34
35
35
# LLM provider
36
36
from langchain_openai import ChatOpenAI
37
+ from langchain_core .language_models import BaseLanguageModel
37
38
38
39
# Configure logging
39
40
logging .basicConfig (level = logging .INFO )
42
43
# Load environment variables
43
44
load_dotenv ()
44
45
45
- # Constants
46
- DEFAULT_WINDOW_WIDTH = 1280
47
- DEFAULT_WINDOW_HEIGHT = 1100
48
- DEFAULT_LOCALE = "en-US"
49
- DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"
50
- DEFAULT_TASK_EXPIRY_MINUTES = 60
51
- DEFAULT_ESTIMATED_TASK_SECONDS = 60
52
- CLEANUP_INTERVAL_SECONDS = 3600 # 1 hour
53
- MAX_AGENT_STEPS = 10
54
-
55
- # Browser configuration arguments
56
- BROWSER_ARGS = [
57
- "--no-sandbox" ,
58
- "--disable-gpu" ,
59
- "--disable-software-rasterizer" ,
60
- "--disable-dev-shm-usage" ,
61
- "--remote-debugging-port=0" , # Use random port to avoid conflicts
62
- ]
46
+
47
+ def init_configuration () -> Dict [str , any ]:
48
+ """
49
+ Initialize configuration from environment variables with defaults.
50
+
51
+ Returns:
52
+ Dictionary containing all configuration parameters
53
+ """
54
+ config = {
55
+ # Browser window settings
56
+ "DEFAULT_WINDOW_WIDTH" : int (os .environ .get ("BROWSER_WINDOW_WIDTH" , 1280 )),
57
+ "DEFAULT_WINDOW_HEIGHT" : int (os .environ .get ("BROWSER_WINDOW_HEIGHT" , 1100 )),
58
+ # Browser config settings
59
+ "DEFAULT_LOCALE" : os .environ .get ("BROWSER_LOCALE" , "en-US" ),
60
+ "DEFAULT_USER_AGENT" : os .environ .get (
61
+ "BROWSER_USER_AGENT" ,
62
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36" ,
63
+ ),
64
+ # Task settings
65
+ "DEFAULT_TASK_EXPIRY_MINUTES" : int (os .environ .get ("TASK_EXPIRY_MINUTES" , 60 )),
66
+ "DEFAULT_ESTIMATED_TASK_SECONDS" : int (
67
+ os .environ .get ("ESTIMATED_TASK_SECONDS" , 60 )
68
+ ),
69
+ "CLEANUP_INTERVAL_SECONDS" : int (
70
+ os .environ .get ("CLEANUP_INTERVAL_SECONDS" , 3600 )
71
+ ), # 1 hour
72
+ "MAX_AGENT_STEPS" : int (os .environ .get ("MAX_AGENT_STEPS" , 10 )),
73
+ # Browser arguments
74
+ "BROWSER_ARGS" : [
75
+ "--no-sandbox" ,
76
+ "--disable-gpu" ,
77
+ "--disable-software-rasterizer" ,
78
+ "--disable-dev-shm-usage" ,
79
+ "--remote-debugging-port=0" , # Use random port to avoid conflicts
80
+ ],
81
+ }
82
+
83
+ return config
84
+
85
+
86
+ # Initialize configuration
87
+ CONFIG = init_configuration ()
63
88
64
89
# Task storage for async operations
65
90
task_store : Dict [str , Dict [str , Any ]] = {}
66
91
67
92
68
93
async def create_browser_context_for_task (
69
94
chrome_path : Optional [str ] = None ,
70
- window_width : int = DEFAULT_WINDOW_WIDTH ,
71
- window_height : int = DEFAULT_WINDOW_HEIGHT ,
72
- locale : str = DEFAULT_LOCALE ,
95
+ window_width : int = CONFIG [ " DEFAULT_WINDOW_WIDTH" ] ,
96
+ window_height : int = CONFIG [ " DEFAULT_WINDOW_HEIGHT" ] ,
97
+ locale : str = CONFIG [ " DEFAULT_LOCALE" ] ,
73
98
) -> Tuple [Browser , BrowserContext ]:
74
99
"""
75
100
Create a fresh browser and context for a task.
@@ -92,7 +117,7 @@ async def create_browser_context_for_task(
92
117
try :
93
118
# Create browser configuration
94
119
browser_config = BrowserConfig (
95
- extra_chromium_args = BROWSER_ARGS ,
120
+ extra_chromium_args = CONFIG [ " BROWSER_ARGS" ] ,
96
121
)
97
122
98
123
# Set chrome path if provided
@@ -109,7 +134,7 @@ async def create_browser_context_for_task(
109
134
minimum_wait_page_load_time = 0.2 ,
110
135
browser_window_size = {"width" : window_width , "height" : window_height },
111
136
locale = locale ,
112
- user_agent = DEFAULT_USER_AGENT ,
137
+ user_agent = CONFIG [ " DEFAULT_USER_AGENT" ] ,
113
138
highlight_elements = True ,
114
139
viewport_expansion = 0 ,
115
140
)
@@ -127,10 +152,10 @@ async def run_browser_task_async(
127
152
task_id : str ,
128
153
url : str ,
129
154
action : str ,
130
- llm : Any ,
131
- window_width : int = DEFAULT_WINDOW_WIDTH ,
132
- window_height : int = DEFAULT_WINDOW_HEIGHT ,
133
- locale : str = DEFAULT_LOCALE ,
155
+ llm : BaseLanguageModel ,
156
+ window_width : int = CONFIG [ " DEFAULT_WINDOW_WIDTH" ] ,
157
+ window_height : int = CONFIG [ " DEFAULT_WINDOW_HEIGHT" ] ,
158
+ locale : str = CONFIG [ " DEFAULT_LOCALE" ] ,
134
159
) -> None :
135
160
"""
136
161
Run a browser task asynchronously and store the result.
@@ -220,7 +245,7 @@ async def done_callback(history: Any) -> None:
220
245
)
221
246
222
247
# Run the agent with a reasonable step limit
223
- agent_result = await agent .run (max_steps = MAX_AGENT_STEPS )
248
+ agent_result = await agent .run (max_steps = CONFIG [ " MAX_AGENT_STEPS" ] )
224
249
225
250
# Get the final result
226
251
final_result = agent_result .final_result ()
@@ -294,7 +319,7 @@ async def cleanup_old_tasks() -> None:
294
319
while True :
295
320
try :
296
321
# Sleep first to avoid cleaning up tasks too early
297
- await asyncio .sleep (CLEANUP_INTERVAL_SECONDS )
322
+ await asyncio .sleep (CONFIG [ " CLEANUP_INTERVAL_SECONDS" ] )
298
323
299
324
current_time = datetime .now ()
300
325
tasks_to_remove = []
@@ -323,11 +348,11 @@ async def cleanup_old_tasks() -> None:
323
348
324
349
325
350
def create_mcp_server (
326
- llm : Any ,
327
- task_expiry_minutes : int = DEFAULT_TASK_EXPIRY_MINUTES ,
328
- window_width : int = DEFAULT_WINDOW_WIDTH ,
329
- window_height : int = DEFAULT_WINDOW_HEIGHT ,
330
- locale : str = DEFAULT_LOCALE ,
351
+ llm : BaseLanguageModel ,
352
+ task_expiry_minutes : int = CONFIG [ " DEFAULT_TASK_EXPIRY_MINUTES" ] ,
353
+ window_width : int = CONFIG [ " DEFAULT_WINDOW_WIDTH" ] ,
354
+ window_height : int = CONFIG [ " DEFAULT_WINDOW_HEIGHT" ] ,
355
+ locale : str = CONFIG [ " DEFAULT_LOCALE" ] ,
331
356
) -> Server :
332
357
"""
333
358
Create and configure an MCP server for browser interaction.
@@ -403,8 +428,8 @@ async def call_tool(
403
428
{
404
429
"task_id" : task_id ,
405
430
"status" : "pending" ,
406
- "message" : f"Browser task started. Please wait for { DEFAULT_ESTIMATED_TASK_SECONDS } seconds, then check the result using browser_get_result or the resource URI. Always wait exactly 5 seconds between status checks." ,
407
- "estimated_time" : f"{ DEFAULT_ESTIMATED_TASK_SECONDS } seconds" ,
431
+ "message" : f"Browser task started. Please wait for { CONFIG [ ' DEFAULT_ESTIMATED_TASK_SECONDS' ] } seconds, then check the result using browser_get_result or the resource URI. Always wait exactly 5 seconds between status checks." ,
432
+ "estimated_time" : f"{ CONFIG [ ' DEFAULT_ESTIMATED_TASK_SECONDS' ] } seconds" ,
408
433
"resource_uri" : f"resource://browser_task/{ task_id } " ,
409
434
"sleep_command" : "sleep 5" ,
410
435
"instruction" : "Use the terminal command 'sleep 5' to wait 5 seconds between status checks. IMPORTANT: Always use exactly 5 seconds, no more and no less." ,
@@ -577,29 +602,21 @@ async def read_resource(uri: str) -> list[types.ResourceContents]:
577
602
578
603
@click .command ()
579
604
@click .option ("--port" , default = 8000 , help = "Port to listen on for SSE" )
580
- @click .option (
581
- "--chrome-path" ,
582
- default = None ,
583
- help = "Path to Chrome executable" ,
584
- )
605
+ @click .option ("--chrome-path" , default = None , help = "Path to Chrome executable" )
585
606
@click .option (
586
607
"--window-width" ,
587
- default = DEFAULT_WINDOW_WIDTH ,
608
+ default = CONFIG [ " DEFAULT_WINDOW_WIDTH" ] ,
588
609
help = "Browser window width" ,
589
610
)
590
611
@click .option (
591
612
"--window-height" ,
592
- default = DEFAULT_WINDOW_HEIGHT ,
613
+ default = CONFIG [ " DEFAULT_WINDOW_HEIGHT" ] ,
593
614
help = "Browser window height" ,
594
615
)
595
- @click .option (
596
- "--locale" ,
597
- default = DEFAULT_LOCALE ,
598
- help = "Browser locale" ,
599
- )
616
+ @click .option ("--locale" , default = CONFIG ["DEFAULT_LOCALE" ], help = "Browser locale" )
600
617
@click .option (
601
618
"--task-expiry-minutes" ,
602
- default = DEFAULT_TASK_EXPIRY_MINUTES ,
619
+ default = CONFIG [ " DEFAULT_TASK_EXPIRY_MINUTES" ] ,
603
620
help = "Minutes after which tasks are considered expired" ,
604
621
)
605
622
def main (
@@ -683,6 +700,21 @@ async def startup_event():
683
700
"""Initialize the server on startup."""
684
701
logger .info ("Starting MCP server..." )
685
702
703
+ # Sanity checks for critical configuration
704
+ if port <= 0 or port > 65535 :
705
+ logger .error (f"Invalid port number: { port } " )
706
+ raise ValueError (f"Invalid port number: { port } " )
707
+
708
+ if window_width <= 0 or window_height <= 0 :
709
+ logger .error (f"Invalid window dimensions: { window_width } x{ window_height } " )
710
+ raise ValueError (
711
+ f"Invalid window dimensions: { window_width } x{ window_height } "
712
+ )
713
+
714
+ if task_expiry_minutes <= 0 :
715
+ logger .error (f"Invalid task expiry minutes: { task_expiry_minutes } " )
716
+ raise ValueError (f"Invalid task expiry minutes: { task_expiry_minutes } " )
717
+
686
718
# Start background task cleanup
687
719
asyncio .create_task (app .cleanup_old_tasks ())
688
720
logger .info ("Task cleanup process scheduled" )
0 commit comments