fix(server): repair login tool and harden upload_cookies
All checks were successful
Build and Push Docker Image / build (push) Successful in 38s
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:
76
server.py
76
server.py
@@ -278,10 +278,54 @@ async def login(
|
|||||||
mcp_logger.info("capture_logs context entered")
|
mcp_logger.info("capture_logs context entered")
|
||||||
if debug:
|
if debug:
|
||||||
mcp_logger.info("DEBUG MODE ENABLED — verbose logging active")
|
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)
|
success = result.get("success", False)
|
||||||
login_manager.record_attempt(success)
|
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)
|
result = _enrich_with_logs(result, log_buf, debug)
|
||||||
mcp_logger.info("capture_logs context exited, returning result")
|
mcp_logger.info("capture_logs context exited, returning result")
|
||||||
return serialize(result)
|
return serialize(result)
|
||||||
@@ -390,9 +434,31 @@ async def upload_cookies(cookies_json: str) -> str:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cookies = json.loads(cookies_json)
|
cookies = json.loads(cookies_json)
|
||||||
with open("cookies.json", "w") as f:
|
|
||||||
json.dump(cookies, f)
|
# Some browser extensions wrap cookies in an object (e.g. {"cookies": [...]})
|
||||||
return json.dumps({"status": "success", "message": "cookies.json updated successfully"})
|
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:
|
except Exception as e:
|
||||||
return json.dumps({"status": "error", "message": str(e)})
|
return json.dumps({"status": "error", "message": str(e)})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user