118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||
import { z } from "zod";
|
||
|
||
import { createCanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
|
||
import { CANVAS_CAPABILITY_REGISTRY } from "@/lib/canvas/capabilities/registry";
|
||
|
||
const requestIdSchema = z.string().trim().min(1).max(180).optional();
|
||
const nodeIdsSchema = z
|
||
.array(z.string().trim().min(1).max(180))
|
||
.min(1)
|
||
.max(50)
|
||
.describe("要操作的画布图片节点 ID;多步调用可传入上一步返回的 createdNodeIds");
|
||
|
||
export async function createCanvasCapabilityMcpServer(params: {
|
||
filterDisabled?: boolean;
|
||
projectId: string;
|
||
requestHeaders: Headers;
|
||
requestUrl: string;
|
||
userId: string;
|
||
}) {
|
||
const server = new McpServer({
|
||
name: "buyi-canvas-capabilities",
|
||
version: "1.0.0",
|
||
});
|
||
const enabledCapabilityIds =
|
||
params.filterDisabled === false
|
||
? new Set(CANVAS_CAPABILITY_REGISTRY.map((capability) => capability.id))
|
||
: await import("@/lib/canvas/capabilities/availability").then(
|
||
({ getEnabledCanvasCapabilityIds }) =>
|
||
getEnabledCanvasCapabilityIds(CANVAS_CAPABILITY_REGISTRY)
|
||
);
|
||
|
||
for (const capability of CANVAS_CAPABILITY_REGISTRY) {
|
||
if (!enabledCapabilityIds.has(capability.id)) continue;
|
||
if (!(capability.mcp.exposed && capability.mcp.toolName)) continue;
|
||
|
||
const inputSchema = z.object({
|
||
nodeIds: nodeIdsSchema,
|
||
args: capability.inputSchema.optional().describe("能力参数"),
|
||
requestId: requestIdSchema.describe("幂等请求号;重试时必须复用同一个值"),
|
||
confirmed: z
|
||
.boolean()
|
||
.optional()
|
||
.describe("危险操作的明确确认;删除等能力必须为 true"),
|
||
});
|
||
|
||
server.registerTool(
|
||
capability.mcp.toolName,
|
||
{
|
||
title: capability.title,
|
||
description: capability.description,
|
||
inputSchema,
|
||
annotations: {
|
||
destructiveHint: capability.risk === "destructive",
|
||
idempotentHint: true,
|
||
openWorldHint: capability.risk === "external",
|
||
readOnlyHint: capability.risk === "read",
|
||
},
|
||
},
|
||
async (input) => {
|
||
try {
|
||
const command = createCanvasCapabilityClientCommand({
|
||
capabilityId: capability.id,
|
||
nodeIds: input.nodeIds,
|
||
projectId: params.projectId,
|
||
requestId: input.requestId,
|
||
args: input.args,
|
||
source: "mcp",
|
||
confirmed: input.confirmed,
|
||
});
|
||
const { executeCanvasCapabilityRuntime } = await import(
|
||
"@/lib/canvas/capabilities/runtime"
|
||
);
|
||
const result = await executeCanvasCapabilityRuntime({
|
||
command,
|
||
projectId: params.projectId,
|
||
requestHeaders: params.requestHeaders,
|
||
requestUrl: params.requestUrl,
|
||
userId: params.userId,
|
||
});
|
||
const createdNodeIds = result.operation?.createdNodeIds ?? [];
|
||
const summary =
|
||
result.mode === "interactive"
|
||
? `已创建客户端交互命令:${capability.title}`
|
||
: `已执行:${capability.title}${
|
||
createdNodeIds.length > 0
|
||
? `,创建 ${createdNodeIds.length} 个节点`
|
||
: ""
|
||
}`;
|
||
|
||
return {
|
||
content: [{ type: "text" as const, text: summary }],
|
||
structuredContent: {
|
||
capabilityId: capability.id,
|
||
mode: result.mode,
|
||
task: result.task,
|
||
...(result.operation ? { operation: result.operation } : {}),
|
||
...(result.outputs ? { outputs: result.outputs } : {}),
|
||
...(result.clientCommand
|
||
? { clientCommand: result.clientCommand }
|
||
: {}),
|
||
},
|
||
};
|
||
} catch (error) {
|
||
const message =
|
||
error instanceof Error ? error.message : `执行${capability.title}失败`;
|
||
return {
|
||
isError: true,
|
||
content: [{ type: "text" as const, text: message }],
|
||
};
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
return server;
|
||
}
|