From 1f0cbfbdc43b9aef00930853e5728f740686b3df Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 13 Oct 2025 16:15:00 +0800 Subject: [PATCH] feat: mcp auth --- web/app/components/base/tab-slider/index.tsx | 3 + web/app/components/tools/mcp/modal.tsx | 155 ++++++++++++++----- web/app/components/tools/types.ts | 6 + web/i18n/en-US/tools.ts | 6 + 4 files changed, 133 insertions(+), 37 deletions(-) diff --git a/web/app/components/base/tab-slider/index.tsx b/web/app/components/base/tab-slider/index.tsx index 55c44d5ea8..7c9364baf9 100644 --- a/web/app/components/base/tab-slider/index.tsx +++ b/web/app/components/base/tab-slider/index.tsx @@ -11,12 +11,14 @@ type Option = { type TabSliderProps = { className?: string value: string + itemClassName?: string | ((active: boolean) => string) onChange: (v: string) => void options: Option[] } const TabSlider: FC = ({ className, + itemClassName, value, onChange, options, @@ -58,6 +60,7 @@ const TabSlider: FC = ({ index === activeIndex ? 'text-text-primary' : 'text-text-tertiary', + typeof itemClassName === 'function' ? itemClassName(index === activeIndex) : itemClassName, )} onClick={() => { if (index !== activeIndex) { diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 1d888c57e8..6b4c99e17a 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useRef, useState } from 'react' +import React, { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { getDomain } from 'tldts' import { RiCloseLine, RiEditLine } from '@remixicon/react' @@ -19,6 +19,9 @@ import { uploadRemoteFileInfo } from '@/service/common' import cn from '@/utils/classnames' import { useHover } from 'ahooks' import { shouldUseMcpIconForAppIcon } from '@/utils/mcp' +import TabSlider from '@/app/components/base/tab-slider' +import { MCPAuthMethod } from '@/app/components/tools/types' +import Switch from '@/app/components/base/switch' export type DuplicateAppModalProps = { data?: ToolWithProvider @@ -63,6 +66,20 @@ const MCPModal = ({ const { t } = useTranslation() const isCreate = !data + const authMethods = [ + { + text: t('tools.mcp.modal.authentication'), + value: MCPAuthMethod.authentication, + }, + { + text: t('tools.mcp.modal.headers'), + value: MCPAuthMethod.headers, + }, + { + text: t('tools.mcp.modal.configurations'), + value: MCPAuthMethod.configurations, + }, + ] const originalServerUrl = data?.server_url const originalServerID = data?.server_identifier const [url, setUrl] = React.useState(data?.server_url || '') @@ -78,6 +95,10 @@ const MCPModal = ({ const [isFetchingIcon, setIsFetchingIcon] = useState(false) const appIconRef = useRef(null) const isHovering = useHover(appIconRef) + const [authMethod, setAuthMethod] = useState(MCPAuthMethod.authentication) + const [useDynamicClientRegistration, setUseDynamicClientRegistration] = useState(data?.use_dynamic_client_registration || false) + const [clientID, setClientID] = useState(data?.client_id || '') + const [credentials, setCredentials] = useState(data?.credentials || '') // Update states when data changes (for edit mode) React.useEffect(() => { @@ -165,6 +186,10 @@ const MCPModal = ({ onHide() } + const handleAuthMethodChange = useCallback((value: string) => { + setAuthMethod(value as MCPAuthMethod) + }, []) + return ( <> )} -
-
- {t('tools.mcp.modal.timeout')} -
- setMcpTimeout(Number(e.target.value))} - onBlur={e => handleBlur(e.target.value.trim())} - placeholder={t('tools.mcp.modal.timeoutPlaceholder')} - /> -
-
-
- {t('tools.mcp.modal.sseReadTimeout')} -
- setSseReadTimeout(Number(e.target.value))} - onBlur={e => handleBlur(e.target.value.trim())} - placeholder={t('tools.mcp.modal.timeoutPlaceholder')} - /> -
-
-
- {t('tools.mcp.modal.headers')} -
-
{t('tools.mcp.modal.headersTip')}
- 0} - /> -
+ { + return `flex-1 ${isActive && 'text-text-accent-light-mode-only'}` + }} + value={authMethod} + onChange={handleAuthMethodChange} + options={authMethods} + /> + { + authMethod === MCPAuthMethod.authentication && ( + <> +
+
+ + {t('tools.mcp.modal.useDynamicClientRegistration')} +
+
+
+
+ {t('tools.mcp.modal.clientID')} +
+ setClientID(e.target.value)} + onBlur={e => handleBlur(e.target.value.trim())} + placeholder={t('tools.mcp.modal.clientID')} + /> +
+
+
+ {t('tools.mcp.modal.credentials')} +
+ setCredentials(e.target.value)} + onBlur={e => handleBlur(e.target.value.trim())} + placeholder={t('tools.mcp.modal.credentialsPlaceholder')} + /> +
+ + ) + } + { + authMethod === MCPAuthMethod.headers && ( +
+
+ {t('tools.mcp.modal.headers')} +
+
{t('tools.mcp.modal.headersTip')}
+ 0} + /> +
+ ) + } + { + authMethod === MCPAuthMethod.configurations && ( + <> +
+
+ {t('tools.mcp.modal.timeout')} +
+ setMcpTimeout(Number(e.target.value))} + onBlur={e => handleBlur(e.target.value.trim())} + placeholder={t('tools.mcp.modal.timeoutPlaceholder')} + /> +
+
+
+ {t('tools.mcp.modal.sseReadTimeout')} +
+ setSseReadTimeout(Number(e.target.value))} + onBlur={e => handleBlur(e.target.value.trim())} + placeholder={t('tools.mcp.modal.timeoutPlaceholder')} + /> +
+ + ) + }
diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 93b15b0533..858b29ea03 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -191,3 +191,9 @@ export type MCPServerDetail = { parameters?: Record headers?: Record } + +export enum MCPAuthMethod { + authentication = 'authentication', + headers = 'headers', + configurations = 'configurations', +} diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 35d5202879..02720ef5bd 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -203,6 +203,12 @@ const translation = { timeout: 'Timeout', sseReadTimeout: 'SSE Read Timeout', timeoutPlaceholder: '30', + authentication: 'Authentication', + useDynamicClientRegistration: 'Use Dynamic Client Registration', + clientID: 'Client ID', + credentials: 'Credentials', + credentialsPlaceholder: 'Client secret', + configurations: 'Configurations', }, delete: 'Remove MCP Server', deleteConfirmTitle: 'Would you like to remove {{mcp}}?',