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/41451](/docs/upgrading/4-14/41451)
|
||||||
- [/docs/upgrading/4-14/4146](/docs/upgrading/4-14/4146)
|
- [/docs/upgrading/4-14/4146](/docs/upgrading/4-14/4146)
|
||||||
- [/docs/upgrading/4-14/4147](/docs/upgrading/4-14/4147)
|
- [/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/40](/docs/upgrading/4-8/40)
|
||||||
- [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41)
|
- [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41)
|
||||||
- [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42)
|
- [/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",
|
"title": "4.14.x",
|
||||||
"description": "",
|
"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/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/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"
|
"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 { i18nT } from '../../../web/i18n/utils';
|
||||||
import z from 'zod';
|
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({
|
export const JsonSchemaPropertiesItemSchema = z.object({
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
'x-tool-description': z.string().optional(),
|
'x-tool-description': z.string().optional(),
|
||||||
type: SchemaInputValueTypeSchema,
|
type: z.any(),
|
||||||
enum: z.array(z.string()).optional(),
|
enum: z.array(z.string()).optional(),
|
||||||
minimum: z.number().optional(),
|
minimum: z.number().optional(),
|
||||||
maximum: 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 type JsonSchemaPropertiesItemType = z.infer<typeof JsonSchemaPropertiesItemSchema>;
|
||||||
|
|
||||||
export const JSONSchemaInputTypeSchema = z.object({
|
export const JSONSchemaInputTypeSchema = z.object({
|
||||||
type: SchemaInputValueTypeSchema,
|
type: z.any().optional(),
|
||||||
properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(),
|
properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(),
|
||||||
required: z.array(z.string()).optional()
|
required: z.array(z.string()).optional()
|
||||||
});
|
});
|
||||||
export type JSONSchemaInputType = z.infer<typeof JSONSchemaInputTypeSchema>;
|
export type JSONSchemaInputType = z.infer<typeof JSONSchemaInputTypeSchema>;
|
||||||
|
|
||||||
export const JSONSchemaOutputTypeSchema = z.object({
|
export const JSONSchemaOutputTypeSchema = z.object({
|
||||||
type: SchemaInputValueTypeSchema,
|
type: z.any().optional(),
|
||||||
properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(),
|
properties: z.record(z.string(), JsonSchemaPropertiesItemSchema).optional(),
|
||||||
required: z.array(z.string()).optional()
|
required: z.array(z.string()).optional()
|
||||||
});
|
});
|
||||||
@@ -47,21 +37,21 @@ export const getNodeInputTypeFromSchemaInputType = ({
|
|||||||
type,
|
type,
|
||||||
arrayItems
|
arrayItems
|
||||||
}: {
|
}: {
|
||||||
type: SchemaInputValueType;
|
type: string;
|
||||||
arrayItems?: { type: SchemaInputValueType };
|
arrayItems?: { type: string };
|
||||||
}) => {
|
}) => {
|
||||||
if (type === 'string') return WorkflowIOValueTypeEnum.string;
|
if (type === 'string') return WorkflowIOValueTypeEnum.string;
|
||||||
if (type === 'number') return WorkflowIOValueTypeEnum.number;
|
if (type === 'number' || type === 'integer') return WorkflowIOValueTypeEnum.number;
|
||||||
if (type === 'integer') return WorkflowIOValueTypeEnum.number;
|
|
||||||
if (type === 'boolean') return WorkflowIOValueTypeEnum.boolean;
|
if (type === 'boolean') return WorkflowIOValueTypeEnum.boolean;
|
||||||
if (type === 'object') return WorkflowIOValueTypeEnum.object;
|
if (type === 'object') return WorkflowIOValueTypeEnum.object;
|
||||||
|
|
||||||
|
if (type !== 'array') return WorkflowIOValueTypeEnum.any;
|
||||||
|
|
||||||
if (!arrayItems) return WorkflowIOValueTypeEnum.arrayAny;
|
if (!arrayItems) return WorkflowIOValueTypeEnum.arrayAny;
|
||||||
|
|
||||||
const itemType = arrayItems.type;
|
const itemType = arrayItems.type;
|
||||||
if (itemType === 'string') return WorkflowIOValueTypeEnum.arrayString;
|
if (itemType === 'string') return WorkflowIOValueTypeEnum.arrayString;
|
||||||
if (itemType === 'number') return WorkflowIOValueTypeEnum.arrayNumber;
|
if (itemType === 'number' || itemType === 'integer') return WorkflowIOValueTypeEnum.arrayNumber;
|
||||||
if (itemType === 'integer') return WorkflowIOValueTypeEnum.arrayNumber;
|
|
||||||
if (itemType === 'boolean') return WorkflowIOValueTypeEnum.arrayBoolean;
|
if (itemType === 'boolean') return WorkflowIOValueTypeEnum.arrayBoolean;
|
||||||
if (itemType === 'object') return WorkflowIOValueTypeEnum.arrayObject;
|
if (itemType === 'object') return WorkflowIOValueTypeEnum.arrayObject;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class MCPClient {
|
|||||||
private client: Client;
|
private client: Client;
|
||||||
private url: string;
|
private url: string;
|
||||||
private headers: Record<string, any> = {};
|
private headers: Record<string, any> = {};
|
||||||
|
private connectionPromise: Promise<Client> | null = null;
|
||||||
|
|
||||||
constructor(config: { url: string; headers: Record<string, any> }) {
|
constructor(config: { url: string; headers: Record<string, any> }) {
|
||||||
this.url = config.url;
|
this.url = config.url;
|
||||||
@@ -28,6 +29,28 @@ export class MCPClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getConnection(): Promise<Client> {
|
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 {
|
try {
|
||||||
const transport = new StreamableHTTPClientTransport(new URL(this.url), {
|
const transport = new StreamableHTTPClientTransport(new URL(this.url), {
|
||||||
requestInit: {
|
requestInit: {
|
||||||
@@ -37,6 +60,7 @@ export class MCPClient {
|
|||||||
await this.client.connect(transport);
|
await this.client.connect(transport);
|
||||||
return this.client;
|
return this.client;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.debug('StreamableHTTP connect failed, falling back to SSE', { url: this.url, error });
|
||||||
await this.client.connect(
|
await this.client.connect(
|
||||||
new SSEClientTransport(new URL(this.url), {
|
new SSEClientTransport(new URL(this.url), {
|
||||||
requestInit: {
|
requestInit: {
|
||||||
@@ -72,6 +96,7 @@ export class MCPClient {
|
|||||||
|
|
||||||
// 内部方法:关闭连接
|
// 内部方法:关闭连接
|
||||||
async closeConnection() {
|
async closeConnection() {
|
||||||
|
this.connectionPromise = null;
|
||||||
try {
|
try {
|
||||||
await retryFn(() => this.client.close(), 3);
|
await retryFn(() => this.client.close(), 3);
|
||||||
logger.debug('MCP client connection closed', { url: this.url });
|
logger.debug('MCP client connection closed', { url: this.url });
|
||||||
|
|||||||
Reference in New Issue
Block a user