fix(server): repair login tool and harden upload_cookies
All checks were successful
Build and Push Docker Image / build (push) Successful in 38s

- login tool was calling api.login() which did not exist in unified_api,
  causing AttributeError on every invocation. Now calls login_to_schwab
  directly with proper credential fallback to config.json.
- upload_cookies hardcoded 'cookies.json' instead of get_cookies_path(),
  and did not handle wrapped export formats ({cookies: [...]}). Both fixed.
- Result envelopes now match the standard {success, data, error, error_type,
  retryable} shape used by other tools.
This commit is contained in:
2026-04-28 04:15:18 +00:00
parent 9f799ee264
commit 8c196b7f65

View File

@@ -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)})