feat: expose local self-correlation tools through MCP
Deploy production / deploy (push) Successful in 57s

This commit is contained in:
yuxuanhui
2026-09-10 09:42:24 +08:00
parent 53b01eb770
commit d4c33d0124
7 changed files with 187 additions and 9 deletions
+106 -1
View File
@@ -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) == 11
assert len(listed.tools) == 13
caps = await client.call_tool("get_research_capabilities", {})
assert caps.structured_content["max_candidates"] == 100
result = await client.call_tool("submit_backtests", submission())
@@ -278,3 +278,108 @@ async def test_refresh_job_error_pages_and_kind_isolation(mcp_app):
assert result["errors"]["items"][0]["alpha_id"] == "1"
error = await mcp_app.state.mcp.invoke(principal, "get_refresh_job", {"job_id": "auth"})
assert error.structured_content["error"]["code"] == "NOT_FOUND"
async def test_self_correlation_sdk_workflow_cache_and_staleness(mcp_app, monkeypatch):
import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
from app.alphas import upsert_alpha
from app.models import Job, SelfCorrelation
from tests.conftest import alpha
from tests.test_alpha_management import points
data = points([1, -2, 4, 3] * 20)
async with mcp_app.state.sessions.begin() as db:
for raw in (alpha("target"), alpha("peer", status="ACTIVE"), alpha("pending")):
await upsert_alpha(db, raw)
db.add(Pnl(alpha_id="target", raw={}, points=data))
calls = []
async def pnl(alpha_id):
calls.append(alpha_id)
return {"records": [{"date": p["date"], "pnl": p["value"]} for p in data]}
monkeypatch.setattr(mcp_app.state.runner.client, "pnl", pnl)
principal, secret = await credentials(mcp_app, {"research:read", "research:refresh"})
async with httpx2.AsyncClient(transport=httpx2.ASGITransport(app=mcp_app),
headers={"Authorization": f"Bearer {secret}"}) as http:
async with streamable_http_client("http://testserver/api/v1/mcp/", http_client=http) as streams:
async with ClientSession(streams[0], streams[1]) as client:
await client.initialize()
listed = {t.name: t for t in (await client.list_tools()).tools}
assert listed["get_self_correlation"].annotations.read_only_hint
assert not listed["check_self_correlation"].annotations.read_only_hint
assert listed["check_self_correlation"].annotations.open_world_hint
before = await client.call_tool("get_self_correlation", {"alpha_id": "target"})
assert before.structured_content["status"] == "not_cached"
assert before.structured_content["result"] is None and calls == []
mcp_app.state.runner.wake.clear()
started = await client.call_tool("check_self_correlation", {"alpha_ids": ["target", "target"]})
assert not started.is_error, started
job_id = started.structured_content["job_id"]
assert mcp_app.state.runner.wake.is_set() and calls == []
assert started.structured_content["alpha_ids"] == ["target"]
again = await client.call_tool("check_self_correlation", {"alpha_ids": ["target"]})
assert again.structured_content["job_id"] == job_id
queued = await client.call_tool("get_refresh_job", {"job_id": job_id})
assert queued.structured_content["status"] == "queued"
await mcp_app.state.runner.execute(job_id)
done = await client.call_tool("get_refresh_job", {"job_id": job_id})
assert done.structured_content["status"] == "completed"
assert done.structured_content["processed"] == 1
result = await client.call_tool("get_self_correlation", {"alpha_id": "target"})
body = result.structured_content
assert body["source"] == "local" and body["status"] == "available"
assert body["result"]["max_correlation"] == pytest.approx(1)
assert body["result"]["compared_count"] == 1
assert body["result"]["matches"][0]["alpha_id"] == "peer"
assert calls == ["peer"]
async with mcp_app.state.sessions.begin() as db:
row = await db.get(SelfCorrelation, "target")
row.stale = True
audit = await db.scalar(select(MCPAudit).where(MCPAudit.tool == "check_self_correlation"))
assert audit.business_id == job_id and audit.result_code == "OK"
assert await db.scalar(select(func.count()).select_from(Job).where(Job.kind == "self_correlation")) == 1
assert await db.get(Pnl, "peer") is not None
stale = await invoke(mcp_app, principal, "get_self_correlation", {"alpha_id": "target"})
assert stale["status"] == "stale" and stale["result"]["stale"] and calls == ["peer"]
async def test_self_correlation_read_only_scope_and_invalid_inputs(mcp_app):
from app.alphas import upsert_alpha
from app.models import Job
from tests.conftest import alpha
async with mcp_app.state.sessions.begin() as db:
await upsert_alpha(db, alpha("known"))
_, secret = await credentials(mcp_app, {"research:read"})
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=mcp_app), base_url="http://testserver",
headers={"Authorization": f"Bearer {secret}", "Accept": "application/json, text/event-stream"}) as http:
listed = await http.post(ENDPOINT, json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"})
names = {t["name"] for t in listed.json()["result"]["tools"]}
assert "get_self_correlation" in names and "check_self_correlation" not in names
for name, args, status in (
("check_self_correlation", {"alpha_ids": ["known"]}, 403),
("get_self_correlation", {"alpha_id": "known"}, 200),
):
response = await http.post(ENDPOINT, json={"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": name, "arguments": args}})
assert response.status_code == status
principal, _ = await credentials(mcp_app)
for args, code in (
({"alpha_ids": []}, "INVALID_INPUT"),
({"alpha_ids": ["known"] * 101}, "INVALID_INPUT"),
({"alpha_ids": ["../secret"]}, "INVALID_INPUT"),
({"alpha_ids": ["known"], "force": True}, "INVALID_INPUT"),
({"alpha_ids": ["known", "missing"]}, "NOT_FOUND"),
):
failure = await mcp_app.state.mcp.invoke(principal, "check_self_correlation", args)
assert failure.is_error and failure.structured_content["error"]["code"] == code
if code == "NOT_FOUND":
assert failure.structured_content["error"]["affected_items"] == ["missing"]
missing = await mcp_app.state.mcp.invoke(principal, "get_self_correlation", {"alpha_id": "missing"})
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