Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions packages/global/core/app/tool/mcpTool/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ export const getMCPToolRuntimeNode = ({
avatar = 'core/app/type/mcpToolsFill',
nodeId,
toolsetName,
toolSetId,
mcpToolSet
toolSetId
}: {
nodeId: string;
tool: McpToolConfigType;
toolSetId: string;
toolsetName: string;
avatar?: string;
mcpToolSet?: NonNullable<NodeToolConfigType['mcpToolSet']>;
}): RuntimeNodeItemType => {
const inputs = jsonSchema2NodeInput({ jsonSchema: tool.inputSchema, schemaType: 'mcp' }).map(
(input) => ({
Expand All @@ -72,8 +70,7 @@ export const getMCPToolRuntimeNode = ({
toolConfig: {
mcpTool: {
toolId: `${AppToolSourceEnum.mcp}-${toolSetId}/${tool.name}` // When runtool is used, parentId and toolname will be employed
},
...(mcpToolSet ? { mcpToolSet } : {})
}
},
jsonSchema: tool.inputSchema,
inputs,
Expand Down
8 changes: 8 additions & 0 deletions packages/global/core/app/tool/toolSet/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import z from 'zod';

/** 工具选择列表和画布使用的纯展示摘要;不依赖执行 Schema,避免编辑器导入循环。 */
export const ToolSetToolSummarySchema = z.object({
name: z.string().meta({ description: '工具名称', example: 'search' }),
description: z.string().meta({ description: '工具能力说明', example: 'Search documents' })
});
export type ToolSetToolSummaryType = z.infer<typeof ToolSetToolSummarySchema>;
11 changes: 3 additions & 8 deletions packages/global/core/workflow/migration/legacy/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
import { migrateSystemConfigToChatConfig } from './systemConfig';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
import { NodeInputKeyEnum, NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../../constants';
import { StoreSecretValueTypeSchema } from '../../../../common/secret/type';
import {
getElseIFLabel,
getHandleId,
isValidReferenceValueFormat,
nodeInputIsReference
} from '../../utils';
import type { NodeToolConfigType } from '../../type/node';

// 这些旧插件没有持久化 catchError,但当前运行时需要显式开启错误分支。
const legacyCatchErrorPluginIds = new Set([
Expand Down Expand Up @@ -48,6 +48,7 @@ const migrateLegacyCanvasStructure = (workflow: CanonicalWorkflowData): Canonica
const legacyMcpToolSet = shouldMigrateLegacyMcpToolSet
? (legacyToolSetValue as Record<string, unknown>)
: undefined;
const legacyHeaderSecret = StoreSecretValueTypeSchema.safeParse(legacyMcpToolSet?.headerSecret);
const inputs = node.inputs.map((input) => {
// 文件列表实际是 JSON 数组;旧的单行 input 控件会导致编辑和解析类型不匹配。
if (
Expand Down Expand Up @@ -149,13 +150,7 @@ const migrateLegacyCanvasStructure = (workflow: CanonicalWorkflowData): Canonica
...node.toolConfig,
mcpToolSet: {
url: typeof legacyMcpToolSet.url === 'string' ? legacyMcpToolSet.url : '',
...(legacyMcpToolSet.headerSecret === undefined
? {}
: {
headerSecret: legacyMcpToolSet.headerSecret as NonNullable<
NodeToolConfigType['mcpToolSet']
>['headerSecret']
}),
...(legacyHeaderSecret.success ? { headerSecret: legacyHeaderSecret.data } : {}),
toolList: Array.isArray(legacyMcpToolSet.toolList) ? legacyMcpToolSet.toolList : []
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/global/core/workflow/type/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ export const FlowNodeInputItemTypeSchema = InputComponentPropsTypeSchema.extend(
});
export type FlowNodeInputItemType = z.infer<typeof FlowNodeInputItemTypeSchema>;

/** MCP/HTTP 引用节点只保留 IO 配置;value/defaultValue 内的业务对象不做递归裁剪。 */
export const ToolReferenceNodeInputTypeSchema = FlowNodeInputItemTypeSchema.omit({
customJsonSchema: true
});

// Workflow node output
export const FlowNodeOutputItemTypeSchema = z.object({
id: z.string().meta({
Expand Down
101 changes: 62 additions & 39 deletions packages/global/core/workflow/type/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FlowNodeTypeEnum, NodeColorSchemaEnum } from '../node/constant';
import { FlowNodeInputItemTypeSchema, FlowNodeOutputItemTypeSchema } from './io';
import { HttpToolConfigTypeSchema } from '../../app/tool/httpTool/type';
import { McpToolConfigSchema } from '../../app/tool/mcpTool/type';
import { ToolSetToolSummarySchema } from '../../app/tool/toolSet/type';
import { ParentIdSchema } from '../../../common/parentFolder/type';
import { InteractiveNodeResponseTypeSchema } from '../template/system/interactive/type';
import { StoreSecretValueTypeSchema } from '../../../common/secret/type';
Expand All @@ -10,23 +11,51 @@ import { SourceMemberSchema } from '../../../support/user/type';
import z from 'zod';
import { BoolSchema, NumSchema } from '../../../common/zod';

export const NodeToolConfigTypeSchema = z.object({
mcpToolSet: z
.object({
url: z.string().meta({
description: 'MCP 服务地址'
}),
headerSecret: StoreSecretValueTypeSchema.nullish().meta({
description: 'MCP 服务请求头密钥配置'
}),
toolList: z.array(McpToolConfigSchema).meta({
description: 'MCP 工具集包含的工具列表'
})
})
.optional()
.meta({
description: '节点绑定的 MCP 工具集配置'
}),
export const McpToolSetRuntimeConfigSchema = z.object({
url: z.string().meta({
description: 'MCP 服务地址'
}),
headerSecret: StoreSecretValueTypeSchema.nullish().meta({
description: 'MCP 服务请求头密钥配置'
}),
toolList: z.array(McpToolConfigSchema).meta({
description: 'MCP 工具集包含的工具列表'
})
});
export type McpToolSetRuntimeConfigType = z.infer<typeof McpToolSetRuntimeConfigSchema>;

export const HttpToolSetRuntimeConfigSchema = z.object({
toolList: z.array(HttpToolConfigTypeSchema).meta({
description: 'HTTP 工具集包含的工具列表'
}),
baseUrl: z.string().optional().meta({
description: 'HTTP 工具集请求基础地址'
}),
apiSchemaStr: z.string().optional().meta({
description: 'HTTP 工具集导入的 OpenAPI Schema 原始内容'
}),
customHeaders: z.string().optional().meta({
description: 'HTTP 工具集公共请求头 JSON 字符串'
}),
headerSecret: StoreSecretValueTypeSchema.nullish().meta({
description: 'HTTP 工具集请求头密钥配置'
})
});
export type HttpToolSetRuntimeConfigType = z.infer<typeof HttpToolSetRuntimeConfigSchema>;

const ToolSetReferenceSchema = z.object({
toolId: z.string().min(1).meta({
description: '工具集 ID'
})
});

const ToolSetPreviewReferenceSchema = ToolSetReferenceSchema.extend({
toolList: z.array(ToolSetToolSummarySchema).optional().meta({
description: '工具集预览展示摘要,不包含执行 Schema'
})
});

const NodeToolConfigCommonSchema = z.object({
mcpTool: z
.object({
toolId: z.string().meta({
Expand Down Expand Up @@ -84,28 +113,6 @@ export const NodeToolConfigTypeSchema = z.object({
.meta({
description: '节点绑定的系统工具集配置'
}),
httpToolSet: z
.object({
toolList: z.array(HttpToolConfigTypeSchema).meta({
description: 'HTTP 工具集包含的工具列表'
}),
baseUrl: z.string().optional().meta({
description: 'HTTP 工具集请求基础地址'
}),
apiSchemaStr: z.string().optional().meta({
description: 'HTTP 工具集导入的 OpenAPI Schema 原始内容'
}),
customHeaders: z.string().optional().meta({
description: 'HTTP 工具集公共请求头 JSON 字符串'
}),
headerSecret: StoreSecretValueTypeSchema.nullish().meta({
description: 'HTTP 工具集请求头密钥配置'
})
})
.optional()
.meta({
description: '节点绑定的 HTTP 工具集配置'
}),
httpTool: z
.object({
toolId: z.string().meta({
Expand All @@ -117,6 +124,22 @@ export const NodeToolConfigTypeSchema = z.object({
description: '节点绑定的 HTTP 单工具配置'
})
});

/** 工作流数据兼容当前引用格式与历史完整快照;引用分支优先,避免空 HTTP toolList 被当成完整配置而丢失 ID。 */
export const NodeToolConfigTypeSchema = NodeToolConfigCommonSchema.extend({
mcpToolSet: z
.union([ToolSetPreviewReferenceSchema, McpToolSetRuntimeConfigSchema])
.optional()
.meta({
description: '兼容历史 MCP 工具集快照或当前工具集引用'
}),
httpToolSet: z
.union([ToolSetPreviewReferenceSchema, HttpToolSetRuntimeConfigSchema])
.optional()
.meta({
description: '兼容历史 HTTP 工具集快照或当前工具集引用'
})
});
export type NodeToolConfigType = z.infer<typeof NodeToolConfigTypeSchema>;

export const ToolDataSchema = z.object({
Expand Down
34 changes: 16 additions & 18 deletions packages/global/core/workflow/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
type ReferenceItemValueType
} from './type/io';
import type { NodeToolConfigType, StoreNodeItemType } from './type/node';
import { ToolSetToolSummarySchema } from '../app/tool/toolSet/type';
import type { AppChatConfigType, AppSchemaType, AppWelcomeConfigType } from '../app/type';
import type { VariableItemType } from '../app/variable/type';
import { normalizeAndParseVariableList } from '../app/variable/utils';
Expand Down Expand Up @@ -415,39 +416,36 @@ export const toolData2FlowNodeIO = ({ nodes }: { nodes: StoreNodeItemType[] }) =
};
};

export const toolSetData2FlowNodeIO = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
/** 工具集预览只携带引用和展示摘要,不携带执行 Schema。 */
export const toolSetData2FlowNodeIO = ({
nodes,
toolSetId
}: {
nodes: StoreNodeItemType[];
toolSetId: string;
}) => {
const toolSetNode = nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.toolSet);

// 加工 toolConfig, 移除一些无需返回客户端以及无需单独存储到 node 的数据。
const toolConfig: NodeToolConfigType | undefined = (() => {
if (!toolSetNode?.toolConfig) return undefined;

if (toolSetNode.toolConfig.httpToolSet) {
const toolList = toolSetNode.toolConfig.httpToolSet.toolList.map((tool) => {
const restTool = { ...tool };
delete restTool.requestSchema;
delete restTool.inputSchema;
delete restTool.outputSchema;
return restTool;
});
const toolSet = toolSetNode.toolConfig.httpToolSet;
return {
...toolSetNode.toolConfig,
httpToolSet: {
toolList
toolId: 'toolId' in toolSet && toolSet.toolId ? toolSet.toolId : toolSetId,
toolList: ToolSetToolSummarySchema.array().parse(toolSet.toolList ?? [])
}
};
}
if (toolSetNode.toolConfig.mcpToolSet) {
const formatToolList = toolSetNode.toolConfig.mcpToolSet.toolList.map((tool) => {
const restTool = { ...tool };
delete restTool.inputSchema;
return restTool;
});
const toolSet = toolSetNode.toolConfig.mcpToolSet;
return {
...toolSetNode.toolConfig,
mcpToolSet: {
url: '',
toolList: formatToolList
// 旧工具集 App 用空 toolId 占位;生成新节点时必须使用真实 App ID。
toolId: 'toolId' in toolSet && toolSet.toolId ? toolSet.toolId : toolSetId,
toolList: ToolSetToolSummarySchema.array().parse(toolSet.toolList ?? [])
}
};
}
Expand Down
38 changes: 38 additions & 0 deletions packages/global/openapi/core/app/tool/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,44 @@ import {
OpenAPIFlowNodeOutputItemTypeSchema
} from '../../workflow/node';
import { BoolSchema } from '../../../../common/zod';
import { ObjectIdSchema } from '../../../../common/type/mongo';
import { AppTypeEnum } from '../../../../core/app/constants';
import { ToolSetToolSummarySchema } from '../../../../core/app/tool/toolSet/type';

/* ============================================================================
* API: 获取工具集子工具摘要
* Route: GET /api/core/app/tool/getToolSetChildren
* Method: GET
* Description: 工具选择列表使用的精简接口,普通目录返回空工具列表及目录类型,不返回执行配置
* Tags: ['工具节点预览', 'HTTP 工具管理', 'MCP 工具管理', 'Read']
* ============================================================================ */
export const GetToolSetChildrenQuerySchema = z.object({
appId: ObjectIdSchema.meta({
description: '待展开的工具集或目录 ID',
example: '507f1f77bcf86cd799439011'
}),
searchKey: z.string().optional().meta({ description: '按工具名搜索的关键字', example: 'search' })
});
export type GetToolSetChildrenQueryType = z.infer<typeof GetToolSetChildrenQuerySchema>;

export const GetToolSetChildrenResponseSchema = z.object({
type: z.enum(AppTypeEnum).meta({
description: '资源类型;非 MCP/HTTP 工具集时由客户端继续加载普通目录',
example: AppTypeEnum.httpToolSet
}),
tools: z
.array(
ToolSetToolSummarySchema.extend({
id: z.string().meta({
description: '可供预览和执行的子工具 ID',
example: 'http-507f1f77bcf86cd799439011/search'
}),
avatar: z.string().meta({ description: '工具图标', example: 'core/app/type/httpToolsFill' })
})
)
.meta({ description: '只含 ID、名称、说明和图标的子工具摘要' })
});
export type GetToolSetChildrenResponseType = z.infer<typeof GetToolSetChildrenResponseSchema>;

const ToolNodeTemplateListItemSchema = NodeTemplateListItemTypeSchema.extend({
toolDescription: z.string().optional().meta({
Expand Down
17 changes: 17 additions & 0 deletions packages/global/openapi/core/app/tool/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { OpenAPIPath } from '../../../type';
import { DevApiTagsMap } from '../../../tag';
import {
GetToolSetChildrenQuerySchema,
GetToolSetChildrenResponseSchema,
GetPreviewNodeQueryOpenAPISchema,
GetPreviewNodeResponseSchema,
GetSystemToolTemplatesBodySchema,
Expand All @@ -10,6 +12,21 @@ import {
} from './api';

export const ToolPath: OpenAPIPath = {
'/core/app/tool/getToolSetChildren': {
get: {
summary: '获取工具集子工具摘要',
description:
'供工具选择列表展开 MCP/HTTP 工具集使用,仅返回展示字段;普通目录返回目录类型和空列表。',
tags: [DevApiTagsMap.toolPreview, DevApiTagsMap.httpTools, DevApiTagsMap.mcpTools],
requestParams: { query: GetToolSetChildrenQuerySchema },
responses: {
200: {
description: '成功获取工具摘要',
content: { 'application/json': { schema: GetToolSetChildrenResponseSchema } }
}
}
}
},
'/core/app/tool/getSystemToolTemplates': {
post: {
summary: '获取系统工具模板列表',
Expand Down
Loading
Loading