feat(mcp): add WorldQuant authentication recovery tools
Deploy production / deploy (push) Successful in 52s
Deploy production / deploy (push) Successful in 52s
This commit is contained in:
@@ -17,6 +17,7 @@ from ..catalog.platform import platform_options, validate_platform_scope
|
||||
from ..catalog.research_metadata import ResearchMetadata, availability_key
|
||||
from ..catalog.service import Catalog
|
||||
from ..correlation import MIN_SAMPLES, THRESHOLD, WINDOW_YEARS
|
||||
from ..jobs import AUTH_KINDS
|
||||
from ..models import Account, Alpha, BacktestItem, Job, JobItem, ResearchRequest, SimulationAttempt, now
|
||||
from ..research.serialization import encode_snapshot
|
||||
from ..research.workspace_contracts import FieldAvailabilityInput
|
||||
@@ -59,6 +60,51 @@ class ResearchAccess:
|
||||
"platform_check": False,
|
||||
}}
|
||||
|
||||
async def connection(self, args):
|
||||
"""Read bounded connection evidence; never return credentials or session cookies."""
|
||||
account = await self.db.get(Account, self.principal.account_id)
|
||||
if not account or account.wq_user_id != self.principal.wq_user_id:
|
||||
raise ResearchError("ACCOUNT_MISMATCH", "平台账户绑定已变化")
|
||||
job = None
|
||||
if args.job_id:
|
||||
job = await self.db.get(Job, args.job_id)
|
||||
if not job or job.kind not in AUTH_KINDS:
|
||||
raise ResearchError("NOT_FOUND", "认证任务不存在")
|
||||
return {"connection_status": account.connection_status,
|
||||
"session_authenticated": self.client.authenticated,
|
||||
"credentials_configured": bool(account.email and account.password_encrypted),
|
||||
"requires_human_verification": account.connection_status == "verification_required",
|
||||
"web_url": f"{self.public_origin}/",
|
||||
"job": {"job_id": job.id, "kind": job.kind, "status": job.status} if job else None,
|
||||
"next_step": "需要人工验证时在网页账户连接面板完成,再调用 authenticate_worldquant(action=verify)"}
|
||||
|
||||
async def authenticate(self, args):
|
||||
"""Queue authentication using saved credentials, sharing the HTTP account lock.
|
||||
|
||||
The caller commits the job and audit together before waking the runner.
|
||||
Human challenges remain in the browser; no password or challenge URL is exposed.
|
||||
"""
|
||||
account = await self.db.scalar(select(Account).where(
|
||||
Account.id == self.principal.account_id).with_for_update())
|
||||
if not account or account.wq_user_id != self.principal.wq_user_id:
|
||||
raise ResearchError("ACCOUNT_MISMATCH", "平台账户绑定已变化")
|
||||
if not account.email or not account.password_encrypted:
|
||||
raise ResearchError("CREDENTIALS_NOT_CONFIGURED", "请先在网页保存 WorldQuant 账户配置")
|
||||
job = await self.db.scalar(select(Job).where(
|
||||
Job.kind.in_(AUTH_KINDS), Job.status.in_(("running", "queued"))))
|
||||
if not job:
|
||||
# Preserve a pending challenge instead of replacing its server-side session.
|
||||
if args.action == "connect" and account.connection_status == "verification_required":
|
||||
raise ResearchError("VERIFICATION_REQUIRED", "请在网页完成人工验证,再调用 action=verify")
|
||||
job = Job(id=str(uuid4()), kind=args.action, payload={}, checkpoint={})
|
||||
self.db.add(job)
|
||||
if args.action == "connect":
|
||||
account.connection_status, account.connection_error = "connecting", None
|
||||
await self.db.flush()
|
||||
self.wake = "jobs"
|
||||
return {"job_id": job.id, "status": job.status, "action": job.kind,
|
||||
"read_with": "get_worldquant_connection", "web_url": f"{self.public_origin}/"}
|
||||
|
||||
async def catalog(self, args):
|
||||
data = await Catalog(self.db).search(args.filters, args.dataset_id)
|
||||
return {**data, "scope": args.filters.model_dump(include={"region", "universe", "delay", "instrument_type"}),
|
||||
|
||||
Reference in New Issue
Block a user