From be9317f6018318d2e38fc7e83e1b636f69750a76 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 24 Feb 2026 13:48:31 +0800 Subject: [PATCH] fix: relax MCP tool JSON Schema Zod validation to handle non-standard types (#6455) * fix: relax MCP tool JSON Schema Zod validation to accept any type values MCP servers may return JSON Schema with type values outside the standard 6 types. Use z.any() for type and items fields to avoid 500 errors on /api/core/app/mcpTools/getTools. - Remove SchemaInputValueTypeSchema enum and SchemaInputValueType - Remove unnecessary .passthrough() - Use plain string type for function parameters Fixes #6451 * fix: mcp adapt --------- Co-authored-by: c121914yu --- document/content/docs/toc.mdx | 1 + document/content/docs/upgrading/4-14/4148.mdx | 18 +++++++++++ .../content/docs/upgrading/4-14/meta.json | 2 +- document/data/doc-last-modified.json | 2 +- packages/global/core/app/jsonschema.ts | 30 +++++++------------ packages/service/core/app/mcp.ts | 25 ++++++++++++++++ 6 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 document/content/docs/upgrading/4-14/4148.mdx diff --git a/document/content/docs/toc.mdx b/document/content/docs/toc.mdx index 2558defb34..6b7c935c5a 100644 --- a/document/content/docs/toc.mdx +++ b/document/content/docs/toc.mdx @@ -121,6 +121,7 @@ description: FastGPT 文档目录 - [/docs/upgrading/4-14/41451](/docs/upgrading/4-14/41451) - [/docs/upgrading/4-14/4146](/docs/upgrading/4-14/4146) - [/docs/upgrading/4-14/4147](/docs/upgrading/4-14/4147) +- [/docs/upgrading/4-14/4148](/docs/upgrading/4-14/4148) - [/docs/upgrading/4-8/40](/docs/upgrading/4-8/40) - [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41) - [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42) diff --git a/document/content/docs/upgrading/4-14/4148.mdx b/document/content/docs/upgrading/4-14/4148.mdx new file mode 100644 index 0000000000..e00f1bb010 --- /dev/null +++ b/document/content/docs/upgrading/4-14/4148.mdx @@ -0,0 +1,18 @@ +--- +title: 'V4.14.8(进行中)' +description: 'FastGPT V4.14.8 更新说明' +--- + + +## 🚀 新增内容 + + + +## ⚙️ 优化 + +1. 兼容 MCP 中 JSON Schema type 类型不在枚举类型里。 + +## 🐛 修复 + +1. 新 SDK 兼容:连续调用同一个 MCP 服务时,多次连接导致报错。 + diff --git a/document/content/docs/upgrading/4-14/meta.json b/document/content/docs/upgrading/4-14/meta.json index 0c89fd4c6e..823147d6ee 100644 --- a/document/content/docs/upgrading/4-14/meta.json +++ b/document/content/docs/upgrading/4-14/meta.json @@ -1,5 +1,5 @@ { "title": "4.14.x", "description": "", - "pages": ["4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] + "pages": ["4148", "4147", "4146", "41451", "4145", "4144", "4143", "4142", "4141", "4140"] } diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 930a55e5e6..00b7bd0b67 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -205,4 +205,4 @@ "document/content/docs/use-cases/external-integration/openapi.mdx": "2026-02-12T18:45:30+08:00", "document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-10T20:07:05+08:00", "document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00" -} \ No newline at end of file +} diff --git a/packages/global/core/app/jsonschema.ts b/packages/global/core/app/jsonschema.ts index 10b7429d8c..557034eadf 100644 --- a/packages/global/core/app/jsonschema.ts +++ b/packages/global/core/app/jsonschema.ts @@ -8,36 +8,26 @@ import type { OpenApiJsonSchema } from './tool/httpTool/type'; import { i18nT } from '../../../web/i18n/utils'; import z from 'zod'; -const SchemaInputValueTypeSchema = z.enum([ - 'string', - 'number', - 'integer', - 'boolean', - 'array', - 'object' -]); -type SchemaInputValueType = z.infer; - export const JsonSchemaPropertiesItemSchema = z.object({ description: z.string().optional(), 'x-tool-description': z.string().optional(), - type: SchemaInputValueTypeSchema, + type: z.any(), enum: z.array(z.string()).optional(), minimum: z.number().optional(), maximum: z.number().optional(), - items: z.object({ type: SchemaInputValueTypeSchema }).optional() + items: z.any().optional() // Array 时候有 }); export type JsonSchemaPropertiesItemType = z.infer; export const JSONSchemaInputTypeSchema = z.object({ - type: SchemaInputValueTypeSchema, + type: z.any().optional(), properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(), required: z.array(z.string()).optional() }); export type JSONSchemaInputType = z.infer; export const JSONSchemaOutputTypeSchema = z.object({ - type: SchemaInputValueTypeSchema, + type: z.any().optional(), properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(), required: z.array(z.string()).optional() }); @@ -47,21 +37,21 @@ export const getNodeInputTypeFromSchemaInputType = ({ type, arrayItems }: { - type: SchemaInputValueType; - arrayItems?: { type: SchemaInputValueType }; + type: string; + arrayItems?: { type: string }; }) => { if (type === 'string') return WorkflowIOValueTypeEnum.string; - if (type === 'number') return WorkflowIOValueTypeEnum.number; - if (type === 'integer') return WorkflowIOValueTypeEnum.number; + if (type === 'number' || type === 'integer') return WorkflowIOValueTypeEnum.number; if (type === 'boolean') return WorkflowIOValueTypeEnum.boolean; if (type === 'object') return WorkflowIOValueTypeEnum.object; + if (type !== 'array') return WorkflowIOValueTypeEnum.any; + if (!arrayItems) return WorkflowIOValueTypeEnum.arrayAny; const itemType = arrayItems.type; if (itemType === 'string') return WorkflowIOValueTypeEnum.arrayString; - if (itemType === 'number') return WorkflowIOValueTypeEnum.arrayNumber; - if (itemType === 'integer') return WorkflowIOValueTypeEnum.arrayNumber; + if (itemType === 'number' || itemType === 'integer') return WorkflowIOValueTypeEnum.arrayNumber; if (itemType === 'boolean') return WorkflowIOValueTypeEnum.arrayBoolean; if (itemType === 'object') return WorkflowIOValueTypeEnum.arrayObject; diff --git a/packages/service/core/app/mcp.ts b/packages/service/core/app/mcp.ts index 1a04c997fb..0799043d71 100644 --- a/packages/service/core/app/mcp.ts +++ b/packages/service/core/app/mcp.ts @@ -17,6 +17,7 @@ export class MCPClient { private client: Client; private url: string; private headers: Record = {}; + private connectionPromise: Promise | null = null; constructor(config: { url: string; headers: Record }) { this.url = config.url; @@ -28,6 +29,28 @@ export class MCPClient { } private async getConnection(): Promise { + if (this.connectionPromise) { + return this.connectionPromise; + } + + this.connectionPromise = this.doConnect().catch((error) => { + // 连接失败时清除缓存,允许下次重试 + this.connectionPromise = null; + throw error; + }); + + this.client.onerror = (error) => { + logger.error('MCP client connection error', { url: this.url, error }); + this.connectionPromise = null; + }; + this.client.onclose = () => { + this.connectionPromise = null; + }; + + return this.connectionPromise; + } + + private async doConnect(): Promise { try { const transport = new StreamableHTTPClientTransport(new URL(this.url), { requestInit: { @@ -37,6 +60,7 @@ export class MCPClient { await this.client.connect(transport); return this.client; } catch (error) { + logger.debug('StreamableHTTP connect failed, falling back to SSE', { url: this.url, error }); await this.client.connect( new SSEClientTransport(new URL(this.url), { requestInit: { @@ -72,6 +96,7 @@ export class MCPClient { // 内部方法:关闭连接 async closeConnection() { + this.connectionPromise = null; try { await retryFn(() => this.client.close(), 3); logger.debug('MCP client connection closed', { url: this.url });