118 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}