diff --git a/frontend/packages/common/src/contexts/GlobalStateContext.tsx b/frontend/packages/common/src/contexts/GlobalStateContext.tsx index a56c9448..99087cd1 100644 --- a/frontend/packages/common/src/contexts/GlobalStateContext.tsx +++ b/frontend/packages/common/src/contexts/GlobalStateContext.tsx @@ -204,7 +204,7 @@ const mockData = [ access: '' }, { - name: 'MCP Key', + name: 'API Key', key: 'mcpKey', path: '/mcpKey', icon: 'material-symbols:key', diff --git a/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx b/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx index 108bd29e..d46a0bdb 100644 --- a/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx +++ b/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx @@ -1,4 +1,4 @@ -import { App, Card, Select } from 'antd' +import { App, Button, Card, Empty, Select } from 'antd' import { $t } from '@common/locales/index.ts' import { Icon } from '@iconify/react/dist/iconify.js' import { useEffect, useState } from 'react' @@ -7,6 +7,9 @@ import { IconButton } from '@common/components/postcat/api/IconButton' import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { useFetch } from '@common/hooks/http' import { useConnection } from './hook/useConnection' +import { ClientRequest, Tool, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' +import { z } from 'zod' +import { useNavigate } from 'react-router-dom' type ConfigList = { openApi?: { @@ -28,13 +31,26 @@ type ApiKeyItem = { value: string } -const IntegrationAIContainer = ({ type, handleApiKeyChange }: { type: 'global' | 'service'; handleApiKeyChange: (value: string) => void }) => { +const IntegrationAIContainer = ({ + type, + handleToolsChange +}: { + type: 'global' | 'service' + handleToolsChange: (value: Tool[]) => void +}) => { const [activeTab, setActiveTab] = useState('mcp') const { message } = App.useApp() const [configContent, setConfigContent] = useState('') const [apiKey, setApiKey] = useState('') const [apiKeyList, setApiKeyList] = useState<{ value: string; label: string }[]>([]) const [mcpServerUrl, setMcpServerUrl] = useState('') + const navigator = useNavigate() + const [errors, setErrors] = useState>({ + resources: null, + prompts: null, + tools: null + }) + const [tabContent, setTabContent] = useState({ mcp: { title: $t('MCP 配置'), @@ -76,7 +92,6 @@ const IntegrationAIContainer = ({ type, handleApiKeyChange }: { type: 'global' | const handleChange = (value: string) => { setApiKey(value) - handleApiKeyChange(value) } /** @@ -106,6 +121,10 @@ const IntegrationAIContainer = ({ type, handleApiKeyChange }: { type: 'global' | }) } + const addKey = () => { + navigator('/mcpKey') + } + /** * 获取 API Key 列表 */ @@ -136,6 +155,41 @@ const IntegrationAIContainer = ({ type, handleApiKeyChange }: { type: 'global' | }) } + const clearError = (tabKey: keyof typeof errors) => { + setErrors((prev) => ({ ...prev, [tabKey]: null })) + } + + const makeRequest = async (request: ClientRequest, schema: T, tabKey?: keyof typeof errors) => { + try { + const response = await makeConnectionRequest(request, schema) + if (tabKey !== undefined) { + clearError(tabKey) + } + return response + } catch (e) { + const errorString = (e as Error).message ?? String(e) + if (tabKey !== undefined) { + setErrors((prev) => ({ + ...prev, + [tabKey]: errorString + })) + } + throw e + } + } + + const listTools = async () => { + const response = await makeRequest( + { + method: 'tools/list' as const, + params: {} + }, + ListToolsResultSchema, + 'tools' + ) + handleToolsChange(response.tools) + } + const { connectionStatus, serverCapabilities, @@ -146,72 +200,54 @@ const IntegrationAIContainer = ({ type, handleApiKeyChange }: { type: 'global' | handleCompletion, completionsSupported, connect: connectMcpServer, - disconnect: disconnectMcpServer, + disconnect: disconnectMcpServer } = useConnection({ transportType: 'sse', - sseUrl: mcpServerUrl, - proxyServerUrl: 'mcp/global/sse', - requestTimeout: 1000, - }); - console.log('connectionStatus==================', connectionStatus); - // console.log('serverCapabilities==================', serverCapabilities); - // console.log('mcpClient==================', mcpClient); - // console.log('requestHistory==================', requestHistory); - // console.log('makeConnectionRequest==================', makeConnectionRequest); - // console.log('sendNotification==================', sendNotification); - // console.log('handleCompletion==================', handleCompletion); - // console.log('completionsSupported==================', completionsSupported); - // console.log('connectMcpServer==================', connectMcpServer); - // console.log('disconnectMcpServer==================', disconnectMcpServer); - // const useConnectAIagent = () => { - // connectMcpServer() - // } + sseUrl: '', + proxyServerUrl: mcpServerUrl, + requestTimeout: 1000 + }) useEffect(() => { - type === 'global' && getGlobalMcpConfig() + if (type === 'global') { + getGlobalMcpConfig() + setMcpServerUrl('mcp/global/sse') + } initTabsData() getKeysList() }, []) useEffect(() => { - if (activeTab === 'openApi') { - setConfigContent(tabContent.openApi?.configContent || '') - } else if (activeTab === 'mcp') { - setConfigContent(tabContent.mcp.configContent || '') + if (activeTab === 'openApi' && tabContent?.openApi?.configContent) { + setConfigContent(tabContent?.openApi?.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}')) + } else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) { + setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}')) } - }, [tabContent, activeTab]) + }, [apiKey, activeTab, tabContent]) + useEffect(() => { - if (configContent && apiKey) { - const parsedConfig = JSON.parse(configContent) - console.log('啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊parsedConfig', parsedConfig, apiKey) - let baseUrl = '' - if (parsedConfig?.mcpServers) { - // 获取 mcpServers 对象中的第一个键 - const serverKey = Object.keys(parsedConfig.mcpServers)[0] - baseUrl = parsedConfig.mcpServers[serverKey]?.url - } - baseUrl = baseUrl.replace('{your_api_key}', apiKey) - if (mcpServerUrl === baseUrl) { - return - } - setMcpServerUrl(baseUrl) - console.log('啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊', mcpServerUrl) + if (mcpServerUrl) { if (connectionStatus === 'connected') { disconnectMcpServer() } connectMcpServer() } - }, [apiKey, configContent, connectMcpServer]) + }, [mcpServerUrl]) + useEffect(() => { + if (connectionStatus === 'connected') { + listTools() + } + }, [connectionStatus]) return ( <> -

+

API Key
- + - - + > +
+ {apiKey} + handleCopy(configContent)} + sx={{ + position: 'absolute', + top: '0px', + right: '5px', + color: '#999', + transition: 'none', + '&.MuiButtonBase-root:hover': { + background: 'transparent', + color: '#3D46F2', + transition: 'none' + } + }} + > +
+
+ + ) : ( + + + + )} )} diff --git a/frontend/packages/core/src/pages/mcpService/McpKeyContainer.tsx b/frontend/packages/core/src/pages/mcpService/McpKeyContainer.tsx index eb4688c6..31c73d41 100644 --- a/frontend/packages/core/src/pages/mcpService/McpKeyContainer.tsx +++ b/frontend/packages/core/src/pages/mcpService/McpKeyContainer.tsx @@ -1,7 +1,7 @@ import InsidePage from '@common/components/aoplatform/InsidePage' import { IconButton } from '@common/components/postcat/api/IconButton' import { $t } from '@common/locales/index.ts' -import { Button, Card, App } from 'antd' +import { Button, Card, App, Empty } from 'antd' import { useFetch } from '@common/hooks/http' import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { useEffect, useRef, useState } from 'react' @@ -101,7 +101,6 @@ const McpKeyContainer = () => { * 编辑 API Key */ const editKey = (key: any) => { - console.log('any', key) modal.confirm({ title: $t('编辑'), content: ( @@ -140,17 +139,36 @@ const McpKeyContainer = () => { {$t('新增 API Key')}
- {keys.map((key, index) => ( - -
-
-

{key.name}

-

- {key.value} + {keys.length ? ( + keys.map((key, index) => ( + +

+
+

{key.name}

+
+ {key.value} + { + copyCode(key?.value) + }} + sx={{ + color: '#333', + transition: 'none', + '&.MuiButtonBase-root:hover': { + background: 'transparent', + color: '#3D46F2', + transition: 'none' + } + }} + > +
+
+
{ - copyCode(key?.value) + editKey(key) }} sx={{ color: '#333', @@ -162,35 +180,26 @@ const McpKeyContainer = () => { } }} > -

+ { + deleteKey(key.id) + }} + sx={{ + color: '#333', + transition: 'none', + '&.MuiButtonBase-root:hover': { background: 'transparent', color: 'red', transition: 'none' } + }} + > +
-
- { - editKey(key) - }} - sx={{ - color: '#333', - transition: 'none', - '&.MuiButtonBase-root:hover': { background: 'transparent', color: '#3D46F2', transition: 'none' } - }} - > - { - deleteKey(key.id) - }} - sx={{ - color: '#333', - transition: 'none', - '&.MuiButtonBase-root:hover': { background: 'transparent', color: 'red', transition: 'none' } - }} - > -
-
- - ))} + + )) + ) : ( + <> + + + )}
diff --git a/frontend/packages/core/src/pages/mcpService/McpServiceContainer.tsx b/frontend/packages/core/src/pages/mcpService/McpServiceContainer.tsx index 308029b1..b8e35ead 100644 --- a/frontend/packages/core/src/pages/mcpService/McpServiceContainer.tsx +++ b/frontend/packages/core/src/pages/mcpService/McpServiceContainer.tsx @@ -1,11 +1,14 @@ import InsidePage from "@common/components/aoplatform/InsidePage" import { $t } from '@common/locales/index.ts' -import { Card } from "antd" import IntegrationAIContainer from "./IntegrationAIContainer" +import { Tool } from "@modelcontextprotocol/sdk/types.js" +import { useState } from "react" +import McpToolsContainer from "./McpToolsContainer" const McpServiceContainer = () => { - const handleApiKeyChange = (value: string) => { - console.log(value) + const [tools, setTools] = useState([]); + const handleToolsChange = (value: Tool[]) => { + setTools(value) } return ( <> @@ -15,11 +18,10 @@ const McpServiceContainer = () => { showBorder={false} scrollPage={false} > +
- - 444 - - + +
diff --git a/frontend/packages/core/src/pages/mcpService/McpToolsContainer.tsx b/frontend/packages/core/src/pages/mcpService/McpToolsContainer.tsx new file mode 100644 index 00000000..b3a98fc8 --- /dev/null +++ b/frontend/packages/core/src/pages/mcpService/McpToolsContainer.tsx @@ -0,0 +1,30 @@ +import { Icon } from '@iconify/react/dist/iconify.js' +import { Tool } from '@modelcontextprotocol/sdk/types.js' +import { Card } from 'antd' + +const McpToolsContainer = ({ tools = [] }: { tools: Tool[] }) => { + return ( + <> + +
+ + Tools +
+ {tools.map((tool, index) => ( + 0 ? 'mt-[10px]' : ''}`}> +

{tool.name}

+
{tool.description}
+
+ ))} +
+ + ) +} + +export default McpToolsContainer diff --git a/frontend/packages/core/src/pages/mcpService/hook/useConnection.ts b/frontend/packages/core/src/pages/mcpService/hook/useConnection.ts index 4b82432e..55365aa6 100644 --- a/frontend/packages/core/src/pages/mcpService/hook/useConnection.ts +++ b/frontend/packages/core/src/pages/mcpService/hook/useConnection.ts @@ -254,16 +254,26 @@ export function useConnection({ const apiPrefix = '/api/v1/'; fullUrl = `${baseUrl}${apiPrefix}${proxyServerUrl}`; } + // let newSseUrl = '' + // if (sseUrl.startsWith('http://') || sseUrl.startsWith('https://')) { + // // 如果是完整URL,直接使用 + // newSseUrl = sseUrl + // } else { + // // 如果是相对路径,添加基础URL和API前缀 + // const baseUrl = window.location.origin; + // const apiPrefix = '/api/v1/'; + // newSseUrl = `${baseUrl}${apiPrefix}${sseUrl}`; + // } const mcpProxyServerUrl = new URL(fullUrl); - mcpProxyServerUrl.searchParams.append("transportType", transportType); - if (transportType === "stdio") { - mcpProxyServerUrl.searchParams.append("command", command || ''); - mcpProxyServerUrl.searchParams.append("args", args || ''); - mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env || {})); - } else { - mcpProxyServerUrl.searchParams.append("url", sseUrl); - } - console.log('sseUrl===', sseUrl) + // mcpProxyServerUrl.searchParams.append("transportType", transportType); + // if (transportType === "stdio") { + // mcpProxyServerUrl.searchParams.append("command", command || ''); + // mcpProxyServerUrl.searchParams.append("args", args || ''); + // mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env || {})); + // } else { + // mcpProxyServerUrl.searchParams.append("url", newSseUrl); + // } + // console.log('sseUrl===', newSseUrl) try { // Inject auth manually instead of using SSEClientTransport, because we're // proxying through the inspector server first.