diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index ff28c23550..084c46cf65 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -11,6 +11,7 @@ import Input from '@/app/components/base/input' import type { AppIconType } from '@/types/app' import type { ToolWithProvider } from '@/app/components/workflow/types' import { noop } from 'lodash-es' +import Toast from '@/app/components/base/toast' import cn from '@/utils/classnames' export type DuplicateAppModalProps = { @@ -57,10 +58,32 @@ const MCPModal = ({ const [url, setUrl] = React.useState(data?.server_url || '') const [showAppIconPicker, setShowAppIconPicker] = useState(false) + const isValidUrl = (string: string) => { + try { + const urlPattern = new RegExp( + '^(https?:\\/\\/)?' // protocol + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain + + '((\\d{1,3}\\.){3}\\d{1,3}))' // IP address + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path + + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string + + '(\\#[-a-z\\d_]*)?$', // anchor + 'i', + ) + return urlPattern.test(string) + } + catch (e) { + return false + } + } + const submit = async () => { + if (!isValidUrl(url)) { + Toast.notify({ type: 'error', message: 'invalid server url' }) + return + } await onConfirm({ name, - server_url: url, + server_url: originalServerUrl === url ? '[__HIDDEN__]' : url, icon_type: appIcon.type, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, @@ -80,6 +103,21 @@ const MCPModal = ({