Nash MCP Server
by nash-app
#!/usr/bin/env python
import os
import sys
import json
import asyncio
import logging
import signal
import atexit
from dotenv import load_dotenv
from browser_use import Agent, Browser, BrowserConfig
from langchain_anthropic import ChatAnthropic
# Configure logging to stderr
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
stream=sys.stderr
)
# Configure browser_use logging
browser_use_logger = logging.getLogger("browser_use")
browser_use_logger.setLevel(logging.INFO)
# Load environment variables from .env file
load_dotenv()
# Global variable to track browser instance for cleanup
_browser_instance = None
# Make sure to clean up browser on exit
def cleanup_browser():
if _browser_instance:
logging.info("Cleaning up browser instance through atexit handler")
loop = asyncio.get_event_loop()
if loop.is_running():
loop.create_task(_browser_instance.close())
else:
# Create a new event loop if the main one is closed
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
new_loop.run_until_complete(_browser_instance.close())
new_loop.close()
# Register cleanup handler
atexit.register(cleanup_browser)
# Handle termination signals
def signal_handler(sig, frame):
logging.info(f"Received signal {sig}, cleaning up and exiting")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
async def run_browser_agent(task):
"""Run the browser-use agent with the given task."""
# Load secrets from environment variable
secrets_path = os.environ.get("NASH_SECRETS_PATH")
if secrets_path and os.path.exists(secrets_path):
try:
with open(secrets_path, 'r') as f:
secrets = json.load(f)
# Add secrets to environment variables
for secret in secrets:
if 'key' in secret and 'value' in secret:
os.environ[secret['key']] = secret['value']
logging.info(f"Loaded secrets from {secrets_path}")
except Exception as e:
logging.error(f"Error loading secrets: {str(e)}")
else:
logging.warning(f"Secrets path not found or invalid: {secrets_path}")
# Get API key from environment (now including any loaded secrets)
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
print(json.dumps({"error": "ANTHROPIC_API_KEY not found in environment variables"}))
return
try:
# Initialize LLM
llm = ChatAnthropic(
model="claude-3-opus-20240229",
anthropic_api_key=api_key,
temperature=0
)
logging.info("LLM initialized successfully")
# Check if Chrome is installed (macOS only since repo specified this is only for macOS)
chrome_path = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
if not os.path.exists(chrome_path):
error_msg = "Google Chrome is not installed. Please install Chrome to use the browser automation feature."
logging.error(f"Chrome not found at {chrome_path}")
print(json.dumps({"error": error_msg}))
return
# Configure browser to use user's Chrome installation
browser = Browser(
config=BrowserConfig(
chrome_instance_path=chrome_path,
)
)
# Store browser in global variable for cleanup
global _browser_instance
_browser_instance = browser
logging.info("Set browser instance for global cleanup tracking")
# Create the agent with configured browser
agent = Agent(
task=task,
llm=llm,
browser=browser,
)
logging.info("Agent created successfully with user's Chrome installation")
try:
# Run the agent with a browser wrapper to ensure closing
result = await agent.run()
logging.info("Agent completed task successfully")
# Extract and return the result
if hasattr(result, 'final_result'):
final_result = result.final_result()
else:
final_result = str(result)
# Return the result as JSON to stdout
print(json.dumps({"result": final_result}))
finally:
# Ensure browser is closed properly
try:
await browser.close()
logging.info("Browser closed successfully")
except Exception as close_error:
logging.error(f"Error closing browser: {str(close_error)}")
# Force exit to ensure browser process is terminated
print(json.dumps({"result": "Task completed but had trouble closing the browser cleanly."}))
# Not calling sys.exit() here as we want controlled shutdown
except Exception as e:
logging.error(f"Error during browser automation: {str(e)}")
print(json.dumps({"error": str(e)}))
def main():
"""Main entry point for the script."""
if len(sys.argv) < 2:
print(json.dumps({"error": "No task provided. Usage: python browser_agent.py 'Your task description'"}))
sys.exit(1)
# Get the task from command line arguments
task = sys.argv[1]
# Run the agent
try:
asyncio.run(run_browser_agent(task))
except Exception as e:
logging.error(f"Unhandled exception: {str(e)}")
print(json.dumps({"error": f"Unhandled exception: {str(e)}"}))
sys.exit(1)
if __name__ == "__main__":
main()