diff --git a/server.py b/server.py index 36a38b7..92e0b0e 100644 --- a/server.py +++ b/server.py @@ -278,10 +278,54 @@ async def login( mcp_logger.info("capture_logs context entered") if debug: mcp_logger.info("DEBUG MODE ENABLED — verbose logging active") - result = await api.login(username=username, password=password, debug=debug) + + # api.login does not exist in unified_api; call the underlying scraper directly + from schwab_scraper.browser.auth import login_to_schwab + from schwab_scraper.core.config import get_schwab_credentials, load_config + + if not username or not password: + config = load_config() + username, password = get_schwab_credentials(config) + + if not username or not password: + result = { + "success": False, + "error": "Username and password are required (or set in config.json)", + "error_type": "AUTHENTICATION", + "retryable": False, + "data": None, + } + else: + try: + cookies = await login_to_schwab(username, password) + if cookies: + result = { + "success": True, + "data": {"cookies_count": len(cookies)}, + "error": None, + "error_type": None, + "retryable": False, + } + else: + result = { + "success": False, + "error": "Login failed — no cookies returned. Check credentials or 2FA status.", + "error_type": "AUTHENTICATION", + "retryable": True, + "data": None, + } + except Exception as exc: + result = { + "success": False, + "error": str(exc), + "error_type": "UNKNOWN", + "retryable": True, + "data": None, + } + success = result.get("success", False) login_manager.record_attempt(success) - mcp_logger.info(f"api.login completed — success={success}") + mcp_logger.info(f"login completed — success={success}") result = _enrich_with_logs(result, log_buf, debug) mcp_logger.info("capture_logs context exited, returning result") return serialize(result) @@ -390,9 +434,31 @@ async def upload_cookies(cookies_json: str) -> str: """ try: cookies = json.loads(cookies_json) - with open("cookies.json", "w") as f: - json.dump(cookies, f) - return json.dumps({"status": "success", "message": "cookies.json updated successfully"}) + + # Some browser extensions wrap cookies in an object (e.g. {"cookies": [...]}) + if isinstance(cookies, dict): + if "cookies" in cookies: + cookies = cookies["cookies"] + else: + return json.dumps({ + "status": "error", + "message": "Expected a list of cookies or an object with a 'cookies' key", + }) + + if not isinstance(cookies, list): + return json.dumps({ + "status": "error", + "message": f"Expected a list of cookies, got {type(cookies).__name__}", + }) + + from schwab_scraper.core.config import get_cookies_path + cookies_path = get_cookies_path() + with open(cookies_path, "w") as f: + json.dump(cookies, f, indent=2) + return json.dumps({ + "status": "success", + "message": f"{cookies_path} updated with {len(cookies)} cookies", + }) except Exception as e: return json.dumps({"status": "error", "message": str(e)})