import { useEffect, useState } from "react"; import { Banner, Button, Input, Toast } from "@douyinfe/semi-ui-19"; import { api, formatTime, post } from "../api"; import "./mcp-keys.css"; type Token = { id: string; name: string; scopes: string[]; created_at: string; expires_at: string; revoked_at: string | null; status: "active" | "expired" | "revoked" | "invalid_binding"; }; type TokenPage = { items: Token[]; total: number; has_more: boolean; enabled: boolean; endpoint: string; can_create: boolean; }; const scopes = [ ["research:read", "读取研究数据", "查询目录、历史、运行与结果"], ["research:refresh", "刷新研究数据", "更新缓存、检查自相关及恢复 WorldQuant 认证"], ["backtests:execute", "执行回测", "提交新的回测批次"], ["backtests:control", "控制回测", "暂停、继续、停止与恢复采集"], ] as const; const labels = { active: "有效", expired: "已过期", revoked: "已撤销", invalid_binding: "账户绑定失效", }; export function MCPKeysPage() { const [data, setData] = useState(null); const [offset, setOffset] = useState(0); const [version, setVersion] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [name, setName] = useState(""); const [days, setDays] = useState("90"); const [permissions, setPermissions] = useState(["research:read"]); const [busy, setBusy] = useState(false); const [created, setCreated] = useState<(Token & { token: string }) | null>( null, ); const [revoking, setRevoking] = useState(null); useEffect(() => { let active = true; setLoading(true); api(`/mcp-tokens?offset=${offset}&limit=25`) .then((next) => { if (active) { setData(next); setError(""); } }) .catch((e: Error) => { if (active) setError(e.message); }) .finally(() => { if (active) setLoading(false); }); return () => { active = false; }; }, [offset, version]); async function create(event: React.FormEvent) { event.preventDefault(); if (busy) return; setBusy(true); setError(""); try { const result = await post("/mcp-tokens", { name: name.trim(), days: Number(days), scopes: permissions, }); setCreated(result); setName(""); setOffset(0); setVersion((v) => v + 1); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function revoke(id: string) { setBusy(true); try { await post(`/mcp-tokens/${id}/revoke`); if (created?.id === id) setCreated(null); setRevoking(null); setVersion((v) => v + 1); Toast.success("MCP Key 已撤销"); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function copy(value: string) { try { await navigator.clipboard.writeText(value); Toast.success("已复制"); } catch { Toast.error("复制失败,请手动选择并复制"); } } return (

MCP Key

管理 Codex 等客户端访问研究数据和执行回测的权限。

{error && } {data && !data.enabled && ( )} {data && !data.can_create && ( )} {data && (

连接地址

{data.endpoint}

客户端使用 Bearer Token 认证。Key 仅用于 MCP,不提供网页管理或正式 Alpha 提交权限。

)} {created ? (

Key 已创建

明文仅在这里显示一次。请保存到客户端凭据配置中;关闭、刷新或离开此页后无法再次查看。

) : (
void create(event)}>

创建 MCP Key

访问权限
{scopes.map(([value, label, description]) => ( ))}
)}

已创建的 Key {data?.total ?? 0}

{data?.items.map((item) => ( ))}
名称 权限 状态 创建时间 到期时间 操作
{item.name} {item.id} {item.scopes.map((scope) => ( {scopes.find(([value]) => value === scope)?.[1] ?? scope} ))} {labels[item.status]} {formatTime(item.created_at)} {formatTime(item.expires_at)} {item.revoked_at ? ( "—" ) : revoking === item.id ? (

撤销后新请求立即失效,已受理回测继续执行。

) : ( )}
{!loading && !data?.items.length &&

暂无 MCP Key。

}
第 {Math.floor(offset / 25) + 1} 页
); }