Expand KOC LOOP MCP operations
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user