v2.7.0: 顶栏新增文档入口,/docs 页提供 MCP 与 Console API(/api/v1)说明(认证、MCP 客户端配置与工具清单、REST 端点与 curl 示例)

This commit is contained in:
bigcode
2026-10-01 02:33:08 +08:00
parent c4b5e65159
commit fa0730c6dd
6 changed files with 216 additions and 1 deletions
+10
View File
@@ -11,6 +11,7 @@ import {
LogoutOutlined, LogoutOutlined,
SettingOutlined, SettingOutlined,
HistoryOutlined, HistoryOutlined,
BookOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { health, logout, me } from "./api.ts"; import { health, logout, me } from "./api.ts";
import { SettingsModal } from "./components/SettingsModal.tsx"; import { SettingsModal } from "./components/SettingsModal.tsx";
@@ -36,6 +37,7 @@ const PAGE_META: Record<string, { title: string; subtitle: string }> = {
"/mailboxes": { title: "邮箱", subtitle: "收件地址、保留策略与邮箱级 Webhook" }, "/mailboxes": { title: "邮箱", subtitle: "收件地址、保留策略与邮箱级 Webhook" },
"/inboxs": { title: "收件箱", subtitle: "已接收邮件(默认保留 1 天,可点保留永不过期),支持按域名筛选" }, "/inboxs": { title: "收件箱", subtitle: "已接收邮件(默认保留 1 天,可点保留永不过期),支持按域名筛选" },
"/logs": { title: "日志", subtitle: "账号 / 域名 / 邮箱 / Webhook 的操作记录(最多保留 10000 条)" }, "/logs": { title: "日志", subtitle: "账号 / 域名 / 邮箱 / Webhook 的操作记录(最多保留 10000 条)" },
"/docs": { title: "文档", subtitle: "MCP 与 Console API(/api/v1)使用说明" },
}; };
/** # 成员变量: 主题切换按钮的太阳/月亮 SVG 图标 **/ /** # 成员变量: 主题切换按钮的太阳/月亮 SVG 图标 **/
@@ -104,6 +106,14 @@ export function App() {
<div className="subtitle">{meta.subtitle}</div> <div className="subtitle">{meta.subtitle}</div>
</div> </div>
<div style={{ display: "flex", alignItems: "center", gap: 14 }}> <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
{/* 文档入口:MCP / API 说明页,位于主题切换左侧 */}
<Button
type="text"
aria-label="打开文档"
title="API 与 MCP 文档"
icon={<BookOutlined />}
onClick={() => navigate("/docs")}
/>
{/* 主题切换:亮↔暗,位于头像左侧 */} {/* 主题切换:亮↔暗,位于头像左侧 */}
<Button <Button
type="text" type="text"
+2
View File
@@ -15,6 +15,7 @@ import { DomainsPage } from "./pages/Domains.tsx";
import { MailboxesPage } from "./pages/Mailboxes.tsx"; import { MailboxesPage } from "./pages/Mailboxes.tsx";
import { InboxPage } from "./pages/Inbox.tsx"; import { InboxPage } from "./pages/Inbox.tsx";
import { LogsPage } from "./pages/Logs.tsx"; import { LogsPage } from "./pages/Logs.tsx";
import { DocsPage } from "./pages/Docs.tsx";
import { setUnauthorizedHandler } from "./api.ts"; import { setUnauthorizedHandler } from "./api.ts";
/** # 成员变量: 路由表(邮件页路由 /inboxs,v2.1.0) **/ /** # 成员变量: 路由表(邮件页路由 /inboxs,v2.1.0) **/
@@ -30,6 +31,7 @@ const router = createBrowserRouter([
{ path: "mailboxes", element: <MailboxesPage /> }, { path: "mailboxes", element: <MailboxesPage /> },
{ path: "inboxs", element: <InboxPage /> }, { path: "inboxs", element: <InboxPage /> },
{ path: "logs", element: <LogsPage /> }, { path: "logs", element: <LogsPage /> },
{ path: "docs", element: <DocsPage /> },
], ],
}, },
]); ]);
+159
View File
@@ -0,0 +1,159 @@
/**
* 文档页(/docs,v2.7.0):MCP 与 Console API(/api/v1)的使用说明。
* 纯静态展示,接口地址按当前访问 origin 动态拼接;顶栏文档图标进入。
*/
import { Alert, Card, Space, Table, Tag, Typography } from "antd";
import { KeyOutlined, ApiOutlined, CodeOutlined } from "@ant-design/icons";
/** # 成员变量: 接口基地址与 MCP 端点(跟随当前访问域名) **/
const API_BASE = `${window.location.origin}/api/v1`;
const MCP_URL = `${window.location.origin}/api/v1/mcp`;
/** # 成员变量: MCP 工具清单(与 /api/v1/mcp tools/list 一致) **/
const MCP_TOOLS = [
{ name: "list_mailboxes", params: "—", desc: "列出所有邮箱(域名、catch-all 标记、保留秒数)" },
{ name: "list_emails", params: "mailbox_id? / limit?", desc: "列出未过期邮件,limit 默认 20、最大 100" },
{ name: "get_email", params: "id", desc: "单封邮件详情(正文 + 附件元数据);过期邮件报错" },
{ name: "create_mailbox", params: "domain_id / address / retention_seconds?", desc: "创建邮箱;address 可传本地部分或完整地址,保留默认 86400 秒" },
{ name: "delete_mailbox", params: "id", desc: "删除邮箱" },
];
/** # 成员变量: REST API 端点清单(与 routes/apiv1.ts 一致) **/
const API_ENDPOINTS = [
{ method: "GET", path: "/accounts", desc: "账号列表(不含已撤销)" },
{ method: "GET", path: "/domains", desc: "域名列表" },
{ method: "GET", path: "/mailboxes", desc: "邮箱列表" },
{ method: "POST", path: "/mailboxes", desc: "创建邮箱,body: { domain_id, address, retention_seconds? };409 已存在" },
{ method: "DELETE", path: "/mailboxes/:id", desc: "删除邮箱" },
{ method: "GET", path: "/emails", desc: "未过期邮件列表,query: mailbox_id? / limit / offset" },
{ method: "GET", path: "/emails/:id", desc: "邮件详情 + 附件元数据;410 已过期" },
];
/** # 函数
* 方法名 -> 彩色 Tag
*/
function MethodTag({ method }: { method: string }) {
const color = method === "GET" ? "blue" : method === "POST" ? "green" : "red";
return <Tag color={color} style={{ fontFamily: "ui-monospace, Menlo, monospace", minWidth: 56, textAlign: "center" }}>{method}</Tag>;
}
/** # 函数
* 文档页:认证说明 + MCP 接入(客户端配置、工具清单)+ REST API(端点清单、curl 示例)
*/
export function DocsPage() {
return (
<Space direction="vertical" size={16} style={{ width: "100%" }}>
<Card
title={
<span>
<KeyOutlined style={{ marginRight: 8 }} />
认证
</span>
}
>
<Typography.Paragraph>
Console API 与 MCP 共用同一个 <b>API/MCP Key</b>(<code className="mono">wmh_</code> 前缀):右上角头像 →{" "}
<b>设置</b> → <b>API / MCP Key</b> 生成。明文仅在生成时显示一次;生成新 Key 后旧 Key 立即失效。
</Typography.Paragraph>
<Typography.Paragraph>
所有请求携带请求头:<code className="mono">Authorization: Bearer wmh_xxxxxxxx</code>
</Typography.Paragraph>
</Card>
<Card
title={
<span>
<ApiOutlined style={{ marginRight: 8 }} />
MCP 接入(Model Context Protocol)
</span>
}
>
<Typography.Paragraph>
端点:<code className="mono">{MCP_URL}</code>(Streamable HTTP,JSON-RPC 2.0,协议版本 2025-06-18;仅接受
POST,无 SSE 流)
</Typography.Paragraph>
<Typography.Paragraph style={{ marginBottom: 8 }}>MCP 客户端配置示例:</Typography.Paragraph>
<pre className="docs-pre">{`{
"mcpServers": {
"wmailhub": {
"type": "http",
"url": "${MCP_URL}",
"headers": { "Authorization": "Bearer wmh_你的Key" }
}
}
}`}</pre>
<Table
size="small"
rowKey="name"
style={{ marginTop: 8 }}
pagination={false}
columns={[
{ title: "工具", dataIndex: "name", width: 160, render: (v: string) => <code className="mono">{v}</code> },
{ title: "参数", dataIndex: "params", width: 260, render: (v: string) => <code className="mono">{v}</code> },
{ title: "说明", dataIndex: "desc" },
]}
dataSource={MCP_TOOLS}
/>
</Card>
<Card
title={
<span>
<CodeOutlined style={{ marginRight: 8 }} />
REST API
</span>
}
>
<Typography.Paragraph>
基地址:<code className="mono">{API_BASE}</code>
</Typography.Paragraph>
<Table
size="small"
rowKey="path"
pagination={false}
columns={[
{
title: "方法",
dataIndex: "method",
width: 90,
render: (v: string) => <MethodTag method={v} />,
},
{
title: "路径",
dataIndex: "path",
width: 220,
render: (v: string) => <code className="mono">{v}</code>,
},
{ title: "说明", dataIndex: "desc" },
]}
dataSource={API_ENDPOINTS}
/>
<Typography.Paragraph style={{ margin: "16px 0 8px" }}>curl 示例:</Typography.Paragraph>
<pre className="docs-pre">{`# 列出邮箱
curl -H "Authorization: Bearer wmh_你的Key" ${API_BASE}/mailboxes
# 创建邮箱(保留 7 天)
curl -X POST -H "Authorization: Bearer wmh_你的Key" \\
-H "Content-Type: application/json" \\
-d '{"domain_id":"dom_xxx","address":"hello","retention_seconds":604800}' \\
${API_BASE}/mailboxes
# 最新 10 封未过期邮件
curl -H "Authorization: Bearer wmh_你的Key" "${API_BASE}/emails?limit=10"`}</pre>
</Card>
<Alert
type="info"
showIcon
message="备注"
description={
<ul style={{ margin: 0, paddingLeft: 18 }}>
<li>邮件读取强制检查未过期:列表不返回过期邮件,详情对已过期邮件返回 410 Gone。</li>
<li>邮箱保留时长默认 86400 秒(1 天);catch-all 邮箱地址形如 <code className="mono">*@域名</code>。</li>
<li>常见状态码:401 未认证(Key 缺失或已失效)、404 资源不存在、409 邮箱已存在、410 邮件已过期。</li>
</ul>
}
/>
</Space>
);
}
+19
View File
@@ -151,6 +151,25 @@ body,
font-size: 12.5px; font-size: 12.5px;
} }
/* 文档页代码块:浅灰底,暗色主题换深底 */
.docs-pre {
margin: 0;
padding: 12px 14px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12.5px;
line-height: 1.7;
background: rgba(5, 5, 5, 0.04);
border: 1px solid rgba(5, 5, 5, 0.08);
border-radius: 8px;
overflow-x: auto;
white-space: pre;
}
[data-theme="dark"] .docs-pre {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.14);
}
.muted { .muted {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wmailhub", "name": "wmailhub",
"version": "2.6.0", "version": "2.7.0",
"description": "WmailHub - 基于 Cloudflare Email Workers 的轻量邮件接收与转发平台(Console 直连 Email Worker)", "description": "WmailHub - 基于 Cloudflare Email Workers 的轻量邮件接收与转发平台(Console 直连 Email Worker)",
"private": true, "private": true,
"type": "module", "type": "module",
+25
View File
@@ -278,6 +278,31 @@ check("勾选后工具条出现「保留选中(批量保留)」按钮", true);
await page.waitForTimeout(400); await page.waitForTimeout(400);
await page.screenshot({ path: `${SHOT_DIR}/16-inbox-retain.png` }); await page.screenshot({ path: `${SHOT_DIR}/16-inbox-retain.png` });
// 7. 文档页:顶栏文档图标进入 /docs,含 MCP 与 REST API 说明
await page.goto(`${BASE}/inboxs`, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".ant-table-tbody", { timeout: 8000 });
check("顶栏文档图标存在(主题切换左侧)", (await page.locator("button[aria-label='打开文档']").count()) === 1);
await page.click("button[aria-label='打开文档']");
await page.waitForURL(`${BASE}/docs`, { timeout: 8000 });
await page.waitForSelector(".ant-card:has-text('MCP 接入')", { timeout: 8000 });
const headerTitle = (await page.locator(".app-header .title").textContent()) ?? "";
const docsText = (await page.locator(".app-content").innerText()) ?? "";
check(
"文档页内容完整(认证 / MCP 接入 / REST API / mcp 端点)",
headerTitle.trim() === "文档" &&
docsText.includes("认证") &&
docsText.includes("MCP 接入") &&
docsText.includes("REST API") &&
docsText.includes("/api/v1/mcp") &&
docsText.includes("Authorization: Bearer wmh_"),
);
check(
"文档页两张清单表(5 个 MCP 工具 + 7 个 API 端点)",
(await page.locator(".ant-table-tbody tr").count()) >= 12,
);
await page.waitForTimeout(500);
await page.screenshot({ path: `${SHOT_DIR}/17-docs.png` });
await browser.close(); await browser.close();
const failed = results.filter((r) => !r.ok); const failed = results.filter((r) => !r.ok);