mirror of
https://github.com/labring/FastGPT.git
synced 2026-02-27 01:02:22 +08:00
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 <yucongcong_test@163.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
18
document/content/docs/upgrading/4-14/4148.mdx
Normal file
18
document/content/docs/upgrading/4-14/4148.mdx
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: 'V4.14.8(进行中)'
|
||||
description: 'FastGPT V4.14.8 更新说明'
|
||||
---
|
||||
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
1. 兼容 MCP 中 JSON Schema type 类型不在枚举类型里。
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. 新 SDK 兼容:连续调用同一个 MCP 服务时,多次连接导致报错。
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<typeof SchemaInputValueTypeSchema>;
|
||||
|
||||
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<typeof JsonSchemaPropertiesItemSchema>;
|
||||
|
||||
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<typeof JSONSchemaInputTypeSchema>;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export class MCPClient {
|
||||
private client: Client;
|
||||
private url: string;
|
||||
private headers: Record<string, any> = {};
|
||||
private connectionPromise: Promise<Client> | null = null;
|
||||
|
||||
constructor(config: { url: string; headers: Record<string, any> }) {
|
||||
this.url = config.url;
|
||||
@@ -28,6 +29,28 @@ export class MCPClient {
|
||||
}
|
||||
|
||||
private async getConnection(): Promise<Client> {
|
||||
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<Client> {
|
||||
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 });
|
||||
|
||||
Reference in New Issue
Block a user