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:
@@ -162,7 +162,7 @@ async def test_official_sdk_client_and_error_contract(mcp_app):
|
||||
async with ClientSession(streams[0], streams[1]) as client:
|
||||
await client.initialize()
|
||||
listed = await client.list_tools()
|
||||
assert len(listed.tools) == 13
|
||||
assert len(listed.tools) == 15
|
||||
caps = await client.call_tool("get_research_capabilities", {})
|
||||
assert caps.structured_content["max_candidates"] == 100
|
||||
result = await client.call_tool("submit_backtests", submission())
|
||||
@@ -383,3 +383,79 @@ async def test_self_correlation_read_only_scope_and_invalid_inputs(mcp_app):
|
||||
assert missing.is_error and missing.structured_content["error"]["code"] == "NOT_FOUND"
|
||||
async with mcp_app.state.sessions() as db:
|
||||
assert await db.scalar(select(func.count()).select_from(Job)) == 0
|
||||
|
||||
|
||||
async def test_worldquant_authentication_queue_and_recovery(mcp_app):
|
||||
from app.models import Account, Job
|
||||
|
||||
principal, _ = await credentials(mcp_app)
|
||||
runner = mcp_app.state.runner
|
||||
runner.client.disconnect()
|
||||
async with mcp_app.state.sessions.begin() as db:
|
||||
account = await db.get(Account, 1)
|
||||
account.connection_status = "disconnected"
|
||||
before = await invoke(mcp_app, principal, "get_worldquant_connection")
|
||||
assert before["credentials_configured"] and not before["session_authenticated"]
|
||||
runner.wake.clear()
|
||||
started = await invoke(mcp_app, principal, "authenticate_worldquant")
|
||||
assert started["status"] == "queued" and runner.wake.is_set()
|
||||
again = await invoke(mcp_app, principal, "authenticate_worldquant")
|
||||
assert again["job_id"] == started["job_id"]
|
||||
await runner.execute(started["job_id"])
|
||||
done = await invoke(mcp_app, principal, "get_worldquant_connection", {"job_id": started["job_id"]})
|
||||
assert done["job"]["status"] == "completed"
|
||||
assert done["connection_status"] == "connected" and done["session_authenticated"]
|
||||
async with mcp_app.state.sessions() as db:
|
||||
assert await db.scalar(select(func.count()).select_from(Job)) == 1
|
||||
audit = await db.scalar(select(MCPAudit).where(MCPAudit.tool == "authenticate_worldquant"))
|
||||
assert audit.business_id == started["job_id"]
|
||||
assert "synthetic-platform-secret" not in str(done)
|
||||
assert "password" not in str(done) and "verification_url" not in str(done)
|
||||
|
||||
|
||||
async def test_worldquant_authentication_permissions_and_challenge(mcp_app):
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.models import Account, Job
|
||||
from app.worldquant import VerificationRequired
|
||||
|
||||
readonly, _ = await credentials(mcp_app, {"research:read"})
|
||||
await invoke(mcp_app, readonly, "get_worldquant_connection")
|
||||
with pytest.raises(HTTPException) as denied:
|
||||
await mcp_app.state.mcp.invoke(readonly, "authenticate_worldquant", {})
|
||||
assert denied.value.status_code == 403
|
||||
principal, _ = await credentials(mcp_app)
|
||||
invalid = await mcp_app.state.mcp.invoke(principal, "authenticate_worldquant", {"password": "untrusted"})
|
||||
assert invalid.is_error and invalid.structured_content["error"]["code"] == "INVALID_INPUT"
|
||||
runner = mcp_app.state.runner
|
||||
original = runner.client.authenticate
|
||||
|
||||
async def challenge(*args, **kwargs):
|
||||
runner.client.verification_url = "https://api.worldquantbrain.com/authentication/test-challenge"
|
||||
raise VerificationRequired(runner.client.verification_url)
|
||||
|
||||
runner.client.authenticate = challenge
|
||||
started = await invoke(mcp_app, principal, "authenticate_worldquant")
|
||||
await runner.execute(started["job_id"])
|
||||
waiting = await invoke(mcp_app, principal, "get_worldquant_connection", {"job_id": started["job_id"]})
|
||||
assert waiting["requires_human_verification"] and waiting["job"]["status"] == "waiting_auth"
|
||||
blocked = await mcp_app.state.mcp.invoke(principal, "authenticate_worldquant", {})
|
||||
assert blocked.structured_content["error"]["code"] == "VERIFICATION_REQUIRED"
|
||||
runner.client.authenticate = original
|
||||
# Simulate completion of the human challenge; keep normal profile/identity verification.
|
||||
async def verified():
|
||||
runner.client.verification_url = None
|
||||
await original("synthetic@example.com", "synthetic-platform-secret", force=True)
|
||||
runner.client.verify = verified
|
||||
resumed = await invoke(mcp_app, principal, "authenticate_worldquant", {"action": "verify"})
|
||||
await runner.execute(resumed["job_id"])
|
||||
done = await invoke(mcp_app, principal, "get_worldquant_connection", {"job_id": resumed["job_id"]})
|
||||
assert done["connection_status"] == "connected" and done["job"]["status"] == "completed"
|
||||
async with mcp_app.state.sessions.begin() as db:
|
||||
account = await db.get(Account, 1)
|
||||
account.password_encrypted = None
|
||||
db.add(Job(id="unrelated", kind="pnl_refresh"))
|
||||
missing = await mcp_app.state.mcp.invoke(principal, "authenticate_worldquant", {})
|
||||
assert missing.structured_content["error"]["code"] == "CREDENTIALS_NOT_CONFIGURED"
|
||||
wrong = await mcp_app.state.mcp.invoke(principal, "get_worldquant_connection", {"job_id": "unrelated"})
|
||||
assert wrong.structured_content["error"]["code"] == "NOT_FOUND"
|
||||
|
||||
Reference in New Issue
Block a user