Expand KOC LOOP MCP operations

This commit is contained in:
巫凤萍
2026-08-07 14:12:52 +08:00
parent 1a9a113229
commit 5670a7939b
15 changed files with 2426 additions and 24 deletions

View File

@@ -1,5 +1,9 @@
import { env } from "cloudflare:workers";
import { createMcpHandler, McpServer } from "@modelcontextprotocol/server";
import {
createMcpHandler,
McpServer,
type McpRequestContext,
} from "@modelcontextprotocol/server";
import { z } from "zod/v4";
import {
FeishuSourceError,
@@ -9,10 +13,12 @@ import {
buildClaimUrl,
createDistributionTask,
} from "../../../lib/task-service";
import { registerMcpOperationTools } from "../../../lib/mcp-tools";
import type { CollectionMcpBindings } from "../../../lib/mcp-collection-client";
export const runtime = "edge";
type McpBindings = FeishuBindings & {
type McpBindings = FeishuBindings & CollectionMcpBindings & {
KOC_MCP_API_KEY?: string;
KOC_PORTAL_URL?: string;
};
@@ -32,12 +38,16 @@ function getBindings() {
return env as unknown as McpBindings;
}
function createServer() {
function createServer(context: McpRequestContext) {
const bindings = getBindings();
const origin = context.requestInfo
? new URL(context.requestInfo.url).origin
: "";
const server = new McpServer(
{ name: "koc-loop", version: "1.0.0" },
{ name: "koc-loop", version: "2.0.0" },
{
instructions:
"用于创建 KOC 内容分发任务。调用前先确认飞书表格链接、任务名称和截止日期;截止日期转换为北京时间 YYYY-MM-DD。相同参数的重试会返回原任务不会重复创建。",
"用于创建和管理 KOC 内容分发任务、数据回收、公开数据采集与账号资源。创建任务前确认飞书链接、任务名和北京时间截止日期;写操作应先向用户说明影响。相同参数的任务创建和采集计划设置支持安全重试。",
},
);
@@ -130,6 +140,8 @@ function createServer() {
},
);
registerMcpOperationTools(server, { bindings, origin });
return server;
}

View File

@@ -15,6 +15,7 @@ import {
type RecoveryWorkbookImage,
type RecoveryWorkbookRow,
} from "../../../lib/recovery-workbook";
import { consumeMcpExportToken } from "../../../lib/mcp-export-token";
export const runtime = "edge";
@@ -218,10 +219,17 @@ function exactArrayBuffer(bytes: Uint8Array) {
}
export async function GET(request: Request) {
if (!(await isAdminRequest(request))) return adminForbidden();
try {
const url = new URL(request.url);
const token = url.searchParams.get("token")?.trim() || "";
const tokenPayload = token
? await consumeMcpExportToken(token, "recovery")
: null;
if (!tokenPayload && !(await isAdminRequest(request))) return adminForbidden();
await ensureSchema();
const taskId = new URL(request.url).searchParams.get("task")?.trim() || "";
const taskId = tokenPayload
? String(tokenPayload.taskId ?? "").trim()
: url.searchParams.get("task")?.trim() || "";
if (!taskId) {
return Response.json({ error: "缺少导出任务" }, { status: 400 });
}

View File

@@ -5,6 +5,7 @@ import {
buildRecoveryWorkbook,
type RecoveryWorkbookRow,
} from "../../../lib/recovery-workbook";
import { consumeMcpExportToken } from "../../../lib/mcp-export-token";
export const runtime = "edge";
@@ -47,21 +48,20 @@ function exactArrayBuffer(bytes: Uint8Array) {
return copy.buffer;
}
export async function POST(request: Request) {
if (!(await isManagerRequest(request))) return managerForbidden();
function normalizeAccountIds(value: unknown) {
if (!Array.isArray(value)) return [];
return [
...new Set(
value
.filter((item): item is string => typeof item === "string")
.map((item) => item.trim())
.filter(Boolean),
),
].slice(0, 5000);
}
async function exportAccounts(accountIds: string[]) {
try {
const body = (await request.json()) as { accountIds?: unknown };
if (!Array.isArray(body.accountIds)) {
return Response.json({ error: "缺少需要导出的账号" }, { status: 400 });
}
const accountIds = [
...new Set(
body.accountIds
.filter((value): value is string => typeof value === "string")
.map((value) => value.trim())
.filter(Boolean),
),
].slice(0, 5000);
if (accountIds.length === 0) {
return Response.json({ error: "当前筛选结果为空" }, { status: 400 });
}
@@ -187,3 +187,28 @@ export async function POST(request: Request) {
);
}
}
export async function POST(request: Request) {
if (!(await isManagerRequest(request))) return managerForbidden();
try {
const body = (await request.json()) as { accountIds?: unknown };
if (!Array.isArray(body.accountIds)) {
return Response.json({ error: "缺少需要导出的账号" }, { status: 400 });
}
return exportAccounts(normalizeAccountIds(body.accountIds));
} catch (error) {
return Response.json(
{ error: error instanceof Error ? error.message : "导出失败" },
{ status: 500 },
);
}
}
export async function GET(request: Request) {
const token = new URL(request.url).searchParams.get("token")?.trim() || "";
const payload = token
? await consumeMcpExportToken(token, "resources")
: null;
if (!payload) return managerForbidden();
return exportAccounts(normalizeAccountIds(payload.accountIds));
}