v2.8.0: MCP 新增 list_domains 工具;create_mailbox/MCP 与 REST 支持 webhook 参数、省略 domain_id 时随机分配已启用 Email Workers 的域名;修复 webhook 校验后置导致的半截创建(先校验再建邮箱);/docs 页与 docs/07 同步
This commit is contained in:
+89
-1
@@ -144,7 +144,7 @@ describe("MCP(/api/v1/mcp,JSON-RPC over HTTP)", () => {
|
||||
);
|
||||
}
|
||||
|
||||
test("initialize + tools/list 返回五个工具与 serverInfo", async () => {
|
||||
test("initialize + tools/list 返回六个工具与 serverInfo", async () => {
|
||||
const t = await makeTestEnv();
|
||||
const cookie = await loginCookie(t);
|
||||
const key = await createKey(t, cookie);
|
||||
@@ -156,6 +156,7 @@ describe("MCP(/api/v1/mcp,JSON-RPC over HTTP)", () => {
|
||||
const tools = await mcpCall(t, key, { jsonrpc: "2.0", id: 2, method: "tools/list" });
|
||||
const toolsData = (await tools.json()) as { result: { tools: { name: string }[] } };
|
||||
expect(toolsData.result.tools.map((x) => x.name)).toEqual([
|
||||
"list_domains",
|
||||
"list_mailboxes",
|
||||
"list_emails",
|
||||
"get_email",
|
||||
@@ -232,4 +233,91 @@ describe("MCP(/api/v1/mcp,JSON-RPC over HTTP)", () => {
|
||||
);
|
||||
expect(get.status).toBe(405);
|
||||
});
|
||||
|
||||
test("list_domains 返回域名;create_mailbox 省略 domain_id 随机分配 + webhook 入库", async () => {
|
||||
const t = await makeTestEnv();
|
||||
const cookie = await loginCookie(t);
|
||||
const key = await createKey(t, cookie);
|
||||
// 种子域名无 zone_id(未启用 Email Workers);再补一个已启用的域名
|
||||
const now = new Date().toISOString();
|
||||
await t.env.DB.prepare(
|
||||
"INSERT INTO domains (id, cloudflare_account_id, account_id, name, status, zone_id, created_at, updated_at) VALUES (?, 'cfacct-1', ?, 'ready.test', 'active', 'zone-ready', ?, ?)",
|
||||
).bind("dom_ready", t.accountId, now, now).run();
|
||||
|
||||
const domains = await mcpCall(t, key, { jsonrpc: "2.0", id: 11, method: "tools/call", params: { name: "list_domains", arguments: {} } });
|
||||
const domainsData = JSON.parse(((await domains.json()) as { result: { content: { text: string }[] } }).result.content[0].text) as {
|
||||
domains: { id: string; name: string }[];
|
||||
};
|
||||
expect(domainsData.domains.map((d) => d.name).sort()).toEqual(["example.test", "ready.test"]);
|
||||
|
||||
// 省略 domain_id:必须落在已启用 Email Workers 的域名上,且 webhook 参数生效
|
||||
const created = await mcpCall(t, key, {
|
||||
jsonrpc: "2.0",
|
||||
id: 12,
|
||||
method: "tools/call",
|
||||
params: { name: "create_mailbox", arguments: { address: "lucky", webhook: "https://hooks.example.com/wmh" } },
|
||||
});
|
||||
const createdData = JSON.parse(((await created.json()) as { result: { content: { text: string }[] } }).result.content[0].text) as {
|
||||
address: string;
|
||||
};
|
||||
expect(createdData.address).toBe("lucky@ready.test");
|
||||
const row = await t.env.DB.prepare("SELECT webhook_url FROM mailboxes WHERE address = ?").bind("lucky@ready.test").first<{ webhook_url: string }>();
|
||||
expect(row?.webhook_url).toBe("https://hooks.example.com/wmh");
|
||||
|
||||
// 带完整地址且省略 domain_id → 明确报错
|
||||
const badAddr = await mcpCall(t, key, {
|
||||
jsonrpc: "2.0",
|
||||
id: 13,
|
||||
method: "tools/call",
|
||||
params: { name: "create_mailbox", arguments: { address: "x@example.test" } },
|
||||
});
|
||||
expect(((await badAddr.json()) as { error: { message: string } }).error.message).toContain("local part");
|
||||
|
||||
// 无已启用域名可用 → no available domain
|
||||
await t.env.DB.prepare("UPDATE domains SET zone_id = NULL WHERE id = 'dom_ready'").run();
|
||||
const none = await mcpCall(t, key, {
|
||||
jsonrpc: "2.0",
|
||||
id: 14,
|
||||
method: "tools/call",
|
||||
params: { name: "create_mailbox", arguments: { address: "lucky2" } },
|
||||
});
|
||||
expect(((await none.json()) as { error: { message: string } }).error.message).toContain("no available domain");
|
||||
});
|
||||
|
||||
test("webhook 校验失败必须整单拒绝,不留半截邮箱(API 与 MCP)", async () => {
|
||||
const t = await makeTestEnv();
|
||||
const cookie = await loginCookie(t);
|
||||
const key = await createKey(t, cookie);
|
||||
await t.env.DB.prepare("UPDATE domains SET zone_id = 'zone-ready' WHERE id = ?").bind(t.domainId).run();
|
||||
|
||||
const restBad = await t.app.fetch(
|
||||
new Request("http://localhost/api/v1/mailboxes", {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ address: "halfbad", webhook: "ftp://example.com/hook" }),
|
||||
}),
|
||||
t.env,
|
||||
);
|
||||
expect(restBad.status).toBe(400);
|
||||
const restRow = await t.env.DB.prepare("SELECT id FROM mailboxes WHERE address = ?").bind("halfbad@example.test").first();
|
||||
expect(restRow).toBeNull();
|
||||
|
||||
const mcpBad = await t.app.fetch(
|
||||
new Request("http://localhost/api/v1/mcp", {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 20,
|
||||
method: "tools/call",
|
||||
params: { name: "create_mailbox", arguments: { address: "mcpbad", webhook: "ftp://example.com/hook" } },
|
||||
}),
|
||||
}),
|
||||
t.env,
|
||||
);
|
||||
const mcpData = (await mcpBad.json()) as { error: { code: number } };
|
||||
expect(mcpData.error.code).toBe(-32602);
|
||||
const mcpRow = await t.env.DB.prepare("SELECT id FROM mailboxes WHERE address = ?").bind("mcpbad@example.test").first();
|
||||
expect(mcpRow).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -297,8 +297,8 @@ check(
|
||||
docsText.includes("Authorization: Bearer wmh_"),
|
||||
);
|
||||
check(
|
||||
"文档页两张清单表(5 个 MCP 工具 + 7 个 API 端点)",
|
||||
(await page.locator(".ant-table-tbody tr").count()) >= 12,
|
||||
"文档页两张清单表(6 个 MCP 工具 + 7 个 API 端点)",
|
||||
(await page.locator(".ant-table-tbody tr").count()) >= 13,
|
||||
);
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/17-docs.png` });
|
||||
|
||||
Reference in New Issue
Block a user