support mcp client streamable http (#4650)

* support mcp streamable http

* fix

* fix

* remove deps
This commit is contained in:
heheer
2025-04-24 23:04:54 +08:00
committed by GitHub
parent 5c93545016
commit 2a54be4d91
16 changed files with 273 additions and 144 deletions

View File

@@ -23,7 +23,7 @@
"@fastgpt/templates": "workspace:*",
"@fastgpt/web": "workspace:*",
"@fortaine/fetch-event-source": "^3.0.6",
"@modelcontextprotocol/sdk": "^1.9.0",
"@modelcontextprotocol/sdk": "^1.10.0",
"@node-rs/jieba": "2.0.1",
"@tanstack/react-query": "^4.24.10",
"ahooks": "^3.7.11",

View File

@@ -15,7 +15,7 @@ import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
import dynamic from 'next/dynamic';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import Markdown from '@/components/Markdown';
import { postRunMCPTools } from '@/web/core/app/api/plugin';
import { postRunMCPTool } from '@/web/core/app/api/plugin';
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
@@ -39,7 +39,7 @@ const ChatTest = ({ currentTool, url }: { currentTool: ToolType | null; url: str
const { runAsync: runTool, loading: isRunning } = useRequest2(
async (data: Record<string, any>) => {
if (!currentTool) return;
return await postRunMCPTools({
return await postRunMCPTool({
params: data,
url,
toolName: currentTool.name

View File

@@ -4,7 +4,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useTranslation } from 'react-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getMCPTools } from '@/web/core/app/api/plugin';
import { AppContext } from '../context';
import { useContextSelector } from 'use-context-selector';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
@@ -12,7 +11,8 @@ import { ToolType } from '@fastgpt/global/core/app/type';
import MyModal from '@fastgpt/web/components/common/MyModal';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { getMCPToolsBody } from '@/pages/api/core/app/mcpTools/getMCPTools';
import type { getMCPToolsBody } from '@/pages/api/support/mcp/client/getTools';
import { getMCPTools } from '@/web/core/app/api/plugin';
const EditForm = ({
url,

View File

@@ -26,7 +26,7 @@ import { useTranslation } from 'react-i18next';
import { AppListContext } from './context';
import { useContextSelector } from 'use-context-selector';
import { ToolType } from '@fastgpt/global/core/app/type';
import { getMCPToolsBody } from '@/pages/api/core/app/mcpTools/getMCPTools';
import type { getMCPToolsBody } from '@/pages/api/support/mcp/client/getTools';
export type MCPToolSetData = {
url: string;
@@ -81,7 +81,7 @@ const MCPToolsEditModal = ({ onClose }: { onClose: () => void }) => {
const { runAsync: runGetMCPTools, loading: isGettingTools } = useRequest2(
(data: getMCPToolsBody) => getMCPTools(data),
{
onSuccess: (res) => {
onSuccess: (res: ToolType[]) => {
setValue('mcpData.toolList', res);
},
errorToast: t('app:MCP_tools_parse_failed')

View File

@@ -11,6 +11,7 @@ import {
getMCPToolRuntimeNode,
getMCPToolSetRuntimeNode
} from '@fastgpt/global/core/app/mcpTools/utils';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
export type createMCPToolsQuery = {};
@@ -61,6 +62,13 @@ async function handler(
}
});
pushTrack.createApp({
type: AppTypeEnum.toolSet,
uid: userId,
teamId,
tmbId
});
return {};
}

View File

@@ -1,43 +0,0 @@
import { NextAPI } from '@/service/middleware/entry';
import { ToolType } from '@fastgpt/global/core/app/type';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
export type getMCPToolsQuery = {};
export type getMCPToolsBody = { url: string };
export type getMCPToolsResponse = ToolType[];
async function handler(
req: ApiRequestProps<getMCPToolsBody, getMCPToolsQuery>,
res: ApiResponseType<getMCPToolsResponse[]>
): Promise<getMCPToolsResponse> {
const { url } = req.body;
const client = new Client({
name: 'FastGPT-MCP-client',
version: '1.0.0'
});
const tools = await (async () => {
try {
const transport = new SSEClientTransport(new URL(url));
await client.connect(transport);
const response = await client.listTools();
return response.tools || [];
} catch (error) {
console.error('Error fetching MCP tools:', error);
return Promise.reject(error);
} finally {
await client.close();
}
})();
return tools as ToolType[];
}
export default NextAPI(handler);

View File

@@ -1,45 +0,0 @@
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
export type RunToolTestQuery = {};
export type RunToolTestBody = {
params: Record<string, any>;
url: string;
toolName: string;
};
export type RunToolTestResponse = any;
async function handler(
req: ApiRequestProps<RunToolTestBody, RunToolTestQuery>,
res: ApiResponseType<RunToolTestResponse>
): Promise<RunToolTestResponse> {
const { params, url, toolName } = req.body;
const client = new Client({
name: 'FastGPT-MCP-client',
version: '1.0.0'
});
const result = await (async () => {
try {
const transport = new SSEClientTransport(new URL(url));
await client.connect(transport);
return await client.callTool({
name: toolName,
arguments: params
});
} catch (error) {
console.error('Error running MCP tool test:', error);
return Promise.reject(error);
} finally {
await client.close();
}
})();
return result;
}
export default NextAPI(handler);

View File

@@ -0,0 +1,28 @@
import { NextAPI } from '@/service/middleware/entry';
import { ToolType } from '@fastgpt/global/core/app/type';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import getMCPClient from '@fastgpt/service/core/app/mcp';
export type getMCPToolsQuery = {};
export type getMCPToolsBody = { url: string };
export type getMCPToolsResponse = ToolType[];
async function handler(
req: ApiRequestProps<getMCPToolsBody, getMCPToolsQuery>,
res: ApiResponseType<getMCPToolsResponse[]>
): Promise<getMCPToolsResponse> {
const { url } = req.body;
const mcpClient = getMCPClient({ url });
try {
const tools = await mcpClient.getTools();
return tools;
} catch (error) {
return Promise.reject(error);
}
}
export default NextAPI(handler);

View File

@@ -0,0 +1,31 @@
import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import getMCPClient from '@fastgpt/service/core/app/mcp';
export type RunMCPToolQuery = {};
export type RunMCPToolBody = {
url: string;
toolName: string;
params: Record<string, any>;
};
export type RunMCPToolResponse = any;
async function handler(
req: ApiRequestProps<RunMCPToolBody, RunMCPToolQuery>,
res: ApiResponseType<RunMCPToolResponse>
): Promise<RunMCPToolResponse> {
const { url, toolName, params } = req.body;
const mcpClient = getMCPClient({ url });
try {
const result = await mcpClient.toolCall(toolName, params);
return result;
} catch (error) {
return Promise.reject(error);
}
}
export default NextAPI(handler);

View File

@@ -19,11 +19,11 @@ import type { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/g
import type { PluginGroupSchemaType } from '@fastgpt/service/core/app/plugin/type';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { defaultGroup } from '@fastgpt/web/core/workflow/constants';
import { createMCPToolsBody } from '@/pages/api/core/app/mcpTools/create';
import type { createMCPToolsBody } from '@/pages/api/core/app/mcpTools/create';
import { ToolType } from '@fastgpt/global/core/app/type';
import { getMCPToolsBody } from '@/pages/api/core/app/mcpTools/getMCPTools';
import { RunToolTestBody } from '@/pages/api/core/app/mcpTools/runTest';
import { updateMCPToolsBody } from '@/pages/api/core/app/mcpTools/update';
import type { updateMCPToolsBody } from '@/pages/api/core/app/mcpTools/update';
import type { RunMCPToolBody } from '@/pages/api/support/mcp/client/runTool';
import type { getMCPToolsBody } from '@/pages/api/support/mcp/client/getTools';
/* ============ team plugin ============== */
export const getTeamPlugTemplates = (data?: ListAppBody) =>
@@ -72,16 +72,16 @@ export const getPreviewPluginNode = (data: GetPreviewNodeQuery) =>
GET<FlowNodeTemplateType>('/core/app/plugin/getPreviewNode', data);
/* ============ mcp tools ============== */
export const getMCPTools = (data: getMCPToolsBody) =>
POST<ToolType[]>('/core/app/mcpTools/getMCPTools', data);
export const postCreateMCPTools = (data: createMCPToolsBody) =>
POST('/core/app/mcpTools/create', data);
export const postUpdateMCPTools = (data: updateMCPToolsBody) =>
POST('/core/app/mcpTools/update', data);
export const postRunMCPTools = (data: RunToolTestBody) => POST('/core/app/mcpTools/runTest', data);
export const getMCPTools = (data: getMCPToolsBody) =>
POST<ToolType[]>('/support/mcp/client/getTools', data);
export const postRunMCPTool = (data: RunMCPToolBody) => POST('/support/mcp/client/runTool', data);
/* ============ http plugin ============== */
export const postCreateHttpPlugin = (data: createHttpPluginBody) =>