### What problem does this PR solve? Feat: List MCP servers #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -0,0 +1,211 @@ | |||
| import message from '@/components/ui/message'; | |||
| import { IMcpServerListResponse } from '@/interfaces/database/mcp'; | |||
| import i18n from '@/locales/config'; | |||
| import mcpServerService from '@/services/mcp-server-service'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { useState } from 'react'; | |||
| export const enum McpApiAction { | |||
| ListMcpServer = 'listMcpServer', | |||
| GetMcpServer = 'getMcpServer', | |||
| CreateMcpServer = 'createMcpServer', | |||
| UpdateMcpServer = 'updateMcpServer', | |||
| DeleteMcpServer = 'deleteMcpServer', | |||
| ImportMcpServer = 'importMcpServer', | |||
| ExportMcpServer = 'exportMcpServer', | |||
| ListMcpServerTools = 'listMcpServerTools', | |||
| TestMcpServerTool = 'testMcpServerTool', | |||
| CacheMcpServerTool = 'cacheMcpServerTool', | |||
| TestMcpServer = 'testMcpServer', | |||
| } | |||
| export const useListMcpServer = () => { | |||
| const { data, isFetching: loading } = useQuery<IMcpServerListResponse>({ | |||
| queryKey: [McpApiAction.ListMcpServer], | |||
| initialData: { total: 0, mcp_servers: [] }, | |||
| gcTime: 0, | |||
| queryFn: async () => { | |||
| const { data } = await mcpServerService.list({}); | |||
| return data?.data; | |||
| }, | |||
| }); | |||
| return { data, loading }; | |||
| }; | |||
| export const useGetMcpServer = () => { | |||
| const [id, setId] = useState(''); | |||
| const { data, isFetching: loading } = useQuery({ | |||
| queryKey: [McpApiAction.GetMcpServer, id], | |||
| initialData: {}, | |||
| gcTime: 0, | |||
| enabled: !!id, | |||
| queryFn: async () => { | |||
| const { data } = await mcpServerService.get(); | |||
| return data?.data ?? {}; | |||
| }, | |||
| }); | |||
| return { data, loading, setId, id }; | |||
| }; | |||
| export const useCreateMcpServer = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.CreateMcpServer], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.create(params); | |||
| if (data.code === 0) { | |||
| message.success(i18n.t(`message.created`)); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [McpApiAction.ListMcpServer], | |||
| }); | |||
| } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, createMcpServer: mutateAsync }; | |||
| }; | |||
| export const useUpdateMcpServer = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.UpdateMcpServer], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.update(params); | |||
| if (data.code === 0) { | |||
| message.success(i18n.t(`message.updated`)); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [McpApiAction.ListMcpServer], | |||
| }); | |||
| } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, updateMcpServer: mutateAsync }; | |||
| }; | |||
| export const useDeleteMcpServer = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.DeleteMcpServer], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.delete(params); | |||
| if (data.code === 0) { | |||
| message.success(i18n.t(`message.deleted`)); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [McpApiAction.ListMcpServer], | |||
| }); | |||
| } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, deleteMcpServer: mutateAsync }; | |||
| }; | |||
| export const useImportMcpServer = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.ImportMcpServer], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.import(params); | |||
| if (data.code === 0) { | |||
| message.success(i18n.t(`message.created`)); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [McpApiAction.ListMcpServer], | |||
| }); | |||
| } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, importMcpServer: mutateAsync }; | |||
| }; | |||
| export const useListMcpServerTools = () => { | |||
| const { data, isFetching: loading } = useQuery({ | |||
| queryKey: [McpApiAction.ListMcpServerTools], | |||
| initialData: [], | |||
| gcTime: 0, | |||
| queryFn: async () => { | |||
| const { data } = await mcpServerService.listTools(); | |||
| return data?.data ?? []; | |||
| }, | |||
| }); | |||
| return { data, loading }; | |||
| }; | |||
| export const useTestMcpServer = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.TestMcpServer], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.test(params); | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, testMcpServer: mutateAsync }; | |||
| }; | |||
| export const useCacheMcpServerTool = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.CacheMcpServerTool], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.cacheTool(params); | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, cacheMcpServerTool: mutateAsync }; | |||
| }; | |||
| export const useTestMcpServerTool = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [McpApiAction.TestMcpServerTool], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await mcpServerService.testTool(params); | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, testMcpServerTool: mutateAsync }; | |||
| }; | |||
| @@ -0,0 +1,15 @@ | |||
| export interface IMcpServer { | |||
| create_date: string; | |||
| description: null; | |||
| id: string; | |||
| name: string; | |||
| server_type: string; | |||
| update_date: string; | |||
| url: string; | |||
| variables: Record<string, any>; | |||
| } | |||
| export interface IMcpServerListResponse { | |||
| mcp_servers: IMcpServer[]; | |||
| total: number; | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| import { ButtonLoading } from '@/components/ui/button'; | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogFooter, | |||
| DialogHeader, | |||
| DialogTitle, | |||
| } from '@/components/ui/dialog'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { EditMcpForm, FormId } from './edit-mcp-form'; | |||
| export function EditMcpDialog({ hideModal, loading, onOk }: IModalProps<any>) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Dialog open onOpenChange={hideModal}> | |||
| <DialogContent> | |||
| <DialogHeader> | |||
| <DialogTitle>Edit profile</DialogTitle> | |||
| </DialogHeader> | |||
| <EditMcpForm onOk={onOk} hideModal={hideModal}></EditMcpForm> | |||
| <DialogFooter> | |||
| <ButtonLoading type="submit" form={FormId} loading={loading}> | |||
| {t('common.save')} | |||
| </ButtonLoading> | |||
| </DialogFooter> | |||
| </DialogContent> | |||
| </Dialog> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,138 @@ | |||
| 'use client'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { RAGFlowSelect } from '@/components/ui/select'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { buildOptions } from '@/utils/form'; | |||
| import { useEffect } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export const FormId = 'EditMcpForm'; | |||
| enum ServerType { | |||
| SSE = 'sse', | |||
| StreamableHttp = 'streamable-http', | |||
| } | |||
| const ServerTypeOptions = buildOptions(ServerType); | |||
| export function EditMcpForm({ | |||
| initialName, | |||
| hideModal, | |||
| onOk, | |||
| }: IModalProps<any> & { initialName?: string }) { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = z.object({ | |||
| name: z | |||
| .string() | |||
| .min(1, { | |||
| message: t('common.namePlaceholder'), | |||
| }) | |||
| .trim(), | |||
| url: z | |||
| .string() | |||
| .min(1, { | |||
| message: t('common.namePlaceholder'), | |||
| }) | |||
| .trim(), | |||
| server_type: z | |||
| .string() | |||
| .min(1, { | |||
| message: t('common.namePlaceholder'), | |||
| }) | |||
| .trim(), | |||
| }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| defaultValues: { name: '', server_type: ServerType.SSE, url: '' }, | |||
| }); | |||
| async function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| const ret = await onOk?.(data); | |||
| if (ret) { | |||
| hideModal?.(); | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| if (initialName) { | |||
| form.setValue('name', initialName); | |||
| } | |||
| }, [form, initialName]); | |||
| return ( | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6" | |||
| id={FormId} | |||
| > | |||
| <FormField | |||
| control={form.control} | |||
| name="name" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('common.name')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('common.namePlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="url" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('common.url')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('common.namePlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="server_type" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('common.serverType')}</FormLabel> | |||
| <FormControl> | |||
| <RAGFlowSelect | |||
| {...field} | |||
| autoComplete="off" | |||
| options={ServerTypeOptions} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| import { Button } from '@/components/ui/button'; | |||
| import { SearchInput } from '@/components/ui/input'; | |||
| import { useListMcpServer } from '@/hooks/use-mcp-request'; | |||
| import { Import, Plus } from 'lucide-react'; | |||
| import { EditMcpDialog } from './edit-mcp-dialog'; | |||
| import { McpCard } from './mcp-card'; | |||
| import { useEditMcp } from './use-edit-mcp'; | |||
| const list = new Array(10).fill('1'); | |||
| export default function McpServer() { | |||
| const { data } = useListMcpServer(); | |||
| const { editVisible, showEditModal, hideEditModal, handleOk } = useEditMcp(); | |||
| return ( | |||
| <section className="p-4"> | |||
| <div className="text-text-title text-2xl">MCP Servers</div> | |||
| <section className="flex items-center justify-between"> | |||
| <div className="text-text-sub-title">自定义 MCP Server 的列表</div> | |||
| <div className="flex gap-5"> | |||
| <SearchInput className="w-40"></SearchInput> | |||
| <Button variant={'secondary'}> | |||
| <Import /> Import | |||
| </Button> | |||
| <Button onClick={showEditModal('')}> | |||
| <Plus /> Add MCP | |||
| </Button> | |||
| </div> | |||
| </section> | |||
| <section className="flex gap-5 flex-wrap pt-5"> | |||
| {data.mcp_servers.map((item) => ( | |||
| <McpCard key={item.id} data={item}></McpCard> | |||
| ))} | |||
| </section> | |||
| {editVisible && ( | |||
| <EditMcpDialog | |||
| hideModal={hideEditModal} | |||
| onOk={handleOk} | |||
| ></EditMcpDialog> | |||
| )} | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| import { MoreButton } from '@/components/more-button'; | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { IMcpServer } from '@/interfaces/database/mcp'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { McpDropdown } from './mcp-dropdown'; | |||
| export type DatasetCardProps = { | |||
| data: IMcpServer; | |||
| }; | |||
| export function McpCard({ data }: DatasetCardProps) { | |||
| const { navigateToAgent } = useNavigatePage(); | |||
| return ( | |||
| <Card key={data.id} className="w-64" onClick={navigateToAgent(data.id)}> | |||
| <CardContent className="p-2.5 pt-2 group"> | |||
| <section className="flex justify-between mb-2"> | |||
| <div className="flex gap-2 items-center"> | |||
| <Avatar className="size-6 rounded-lg"> | |||
| <AvatarImage src={data?.avatar} /> | |||
| <AvatarFallback className="rounded-lg ">CN</AvatarFallback> | |||
| </Avatar> | |||
| </div> | |||
| <McpDropdown> | |||
| <MoreButton></MoreButton> | |||
| </McpDropdown> | |||
| </section> | |||
| <div className="flex justify-between items-end"> | |||
| <div className="w-full"> | |||
| <h3 className="text-lg font-semibold mb-2 line-clamp-1"> | |||
| {data.name} | |||
| </h3> | |||
| <p className="text-xs text-text-sub-title">{data.description}</p> | |||
| <p className="text-xs text-text-sub-title"> | |||
| {formatDate(data.update_date)} | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; | |||
| import { | |||
| DropdownMenu, | |||
| DropdownMenuContent, | |||
| DropdownMenuItem, | |||
| DropdownMenuSeparator, | |||
| DropdownMenuTrigger, | |||
| } from '@/components/ui/dropdown-menu'; | |||
| import { PenLine, Trash2 } from 'lucide-react'; | |||
| import { MouseEventHandler, PropsWithChildren, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export function McpDropdown({ children }: PropsWithChildren) { | |||
| const { t } = useTranslation(); | |||
| const handleShowAgentRenameModal: MouseEventHandler<HTMLDivElement> = | |||
| useCallback((e) => { | |||
| e.stopPropagation(); | |||
| }, []); | |||
| const handleDelete: MouseEventHandler<HTMLDivElement> = | |||
| useCallback(() => {}, []); | |||
| return ( | |||
| <DropdownMenu> | |||
| <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger> | |||
| <DropdownMenuContent> | |||
| <DropdownMenuItem onClick={handleShowAgentRenameModal}> | |||
| {t('common.rename')} <PenLine /> | |||
| </DropdownMenuItem> | |||
| <DropdownMenuSeparator /> | |||
| <ConfirmDeleteDialog onOk={handleDelete}> | |||
| <DropdownMenuItem | |||
| className="text-text-delete-red" | |||
| onSelect={(e) => { | |||
| e.preventDefault(); | |||
| }} | |||
| onClick={(e) => { | |||
| e.stopPropagation(); | |||
| }} | |||
| > | |||
| {t('common.delete')} <Trash2 /> | |||
| </DropdownMenuItem> | |||
| </ConfirmDeleteDialog> | |||
| </DropdownMenuContent> | |||
| </DropdownMenu> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { | |||
| useCreateMcpServer, | |||
| useGetMcpServer, | |||
| useUpdateMcpServer, | |||
| } from '@/hooks/use-mcp-request'; | |||
| import { useCallback } from 'react'; | |||
| export const useEditMcp = () => { | |||
| const { | |||
| visible: editVisible, | |||
| hideModal: hideEditModal, | |||
| showModal: showEditModal, | |||
| } = useSetModalState(); | |||
| const { createMcpServer, loading } = useCreateMcpServer(); | |||
| const { data, setId, id } = useGetMcpServer(); | |||
| const { updateMcpServer } = useUpdateMcpServer(); | |||
| const handleShowModal = useCallback( | |||
| (id?: string) => () => { | |||
| if (id) { | |||
| setId(id); | |||
| } | |||
| showEditModal(); | |||
| }, | |||
| [setId, showEditModal], | |||
| ); | |||
| const handleOk = useCallback( | |||
| async (values: any) => { | |||
| if (id) { | |||
| updateMcpServer(values); | |||
| } else { | |||
| createMcpServer(values); | |||
| } | |||
| }, | |||
| [createMcpServer, id, updateMcpServer], | |||
| ); | |||
| return { | |||
| editVisible, | |||
| hideEditModal, | |||
| showEditModal: handleShowModal, | |||
| loading, | |||
| createMcpServer, | |||
| detail: data, | |||
| handleOk, | |||
| }; | |||
| }; | |||
| @@ -1,8 +1,5 @@ | |||
| import { | |||
| ProfileSettingBaseKey, | |||
| ProfileSettingRouteKey, | |||
| } from '@/constants/setting'; | |||
| import { useLogout } from '@/hooks/login-hooks'; | |||
| import { Routes } from '@/routes'; | |||
| import { useCallback } from 'react'; | |||
| import { useNavigate } from 'umi'; | |||
| @@ -11,11 +8,11 @@ export const useHandleMenuClick = () => { | |||
| const { logout } = useLogout(); | |||
| const handleMenuClick = useCallback( | |||
| (key: ProfileSettingRouteKey) => () => { | |||
| if (key === ProfileSettingRouteKey.Logout) { | |||
| (key: Routes) => () => { | |||
| if (key === Routes.Logout) { | |||
| logout(); | |||
| } else { | |||
| navigate(`/${ProfileSettingBaseKey}/${key}`); | |||
| navigate(`${Routes.ProfileSetting}${key}`); | |||
| } | |||
| }, | |||
| [logout, navigate], | |||
| @@ -2,10 +2,10 @@ import { useIsDarkTheme, useTheme } from '@/components/theme-provider'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Label } from '@/components/ui/label'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { ProfileSettingRouteKey } from '@/constants/setting'; | |||
| import { useLogout } from '@/hooks/login-hooks'; | |||
| import { useSecondPathName } from '@/hooks/route-hook'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Routes } from '@/routes'; | |||
| import { | |||
| AlignEndVertical, | |||
| Banknote, | |||
| @@ -22,9 +22,10 @@ const menuItems = [ | |||
| { | |||
| section: 'Account & collaboration', | |||
| items: [ | |||
| { icon: User, label: 'Profile', key: ProfileSettingRouteKey.Profile }, | |||
| { icon: LayoutGrid, label: 'Team', key: ProfileSettingRouteKey.Team }, | |||
| { icon: Banknote, label: 'Plan', key: ProfileSettingRouteKey.Plan }, | |||
| { icon: User, label: 'Profile', key: Routes.Profile }, | |||
| { icon: LayoutGrid, label: 'Team', key: Routes.Team }, | |||
| { icon: Banknote, label: 'Plan', key: Routes.Plan }, | |||
| { icon: Banknote, label: 'MCP', key: Routes.Mcp }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -33,17 +34,17 @@ const menuItems = [ | |||
| { | |||
| icon: Box, | |||
| label: 'Model management', | |||
| key: ProfileSettingRouteKey.Model, | |||
| key: Routes.Model, | |||
| }, | |||
| { | |||
| icon: FileCog, | |||
| label: 'Prompt management', | |||
| key: ProfileSettingRouteKey.Prompt, | |||
| key: Routes.Prompt, | |||
| }, | |||
| { | |||
| icon: AlignEndVertical, | |||
| label: 'Chunking method', | |||
| key: ProfileSettingRouteKey.Chunk, | |||
| key: Routes.Chunk, | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -1,5 +1,6 @@ | |||
| export enum Routes { | |||
| Login = '/login', | |||
| Logout = '/logout', | |||
| Home = '/home', | |||
| Datasets = '/datasets', | |||
| DatasetBase = '/dataset', | |||
| @@ -13,6 +14,18 @@ export enum Routes { | |||
| Chat = '/next-chat', | |||
| Files = '/files', | |||
| ProfileSetting = '/profile-setting', | |||
| Profile = '/profile', | |||
| Mcp = '/mcp', | |||
| Team = '/team', | |||
| Plan = '/plan', | |||
| Model = '/model', | |||
| Prompt = '/prompt', | |||
| ProfileMcp = `${ProfileSetting}${Mcp}`, | |||
| ProfileTeam = `${ProfileSetting}${Team}`, | |||
| ProfilePlan = `${ProfileSetting}${Plan}`, | |||
| ProfileModel = `${ProfileSetting}${Model}`, | |||
| ProfilePrompt = `${ProfileSetting}${Prompt}`, | |||
| ProfileProfile = `${ProfileSetting}${Profile}`, | |||
| DatasetTesting = '/testing', | |||
| DatasetSetting = '/setting', | |||
| Chunk = '/chunk', | |||
| @@ -303,27 +316,31 @@ const routes = [ | |||
| routes: [ | |||
| { | |||
| path: Routes.ProfileSetting, | |||
| redirect: `${Routes.ProfileSetting}/profile`, | |||
| redirect: `${Routes.ProfileProfile}`, | |||
| }, | |||
| { | |||
| path: `${Routes.ProfileSetting}/profile`, | |||
| component: `@/pages${Routes.ProfileSetting}/profile`, | |||
| path: `${Routes.ProfileProfile}`, | |||
| component: `@/pages${Routes.ProfileProfile}`, | |||
| }, | |||
| { | |||
| path: `${Routes.ProfileSetting}/team`, | |||
| component: `@/pages${Routes.ProfileSetting}/team`, | |||
| path: `${Routes.ProfileTeam}`, | |||
| component: `@/pages${Routes.ProfileTeam}`, | |||
| }, | |||
| { | |||
| path: `${Routes.ProfileSetting}/plan`, | |||
| component: `@/pages${Routes.ProfileSetting}/plan`, | |||
| path: `${Routes.ProfilePlan}`, | |||
| component: `@/pages${Routes.ProfilePlan}`, | |||
| }, | |||
| { | |||
| path: `${Routes.ProfileSetting}/model`, | |||
| component: `@/pages${Routes.ProfileSetting}/model`, | |||
| path: `${Routes.ProfileModel}`, | |||
| component: `@/pages${Routes.ProfileModel}`, | |||
| }, | |||
| { | |||
| path: `${Routes.ProfileSetting}/prompt`, | |||
| component: `@/pages${Routes.ProfileSetting}/prompt`, | |||
| path: `${Routes.ProfilePrompt}`, | |||
| component: `@/pages${Routes.ProfilePrompt}`, | |||
| }, | |||
| { | |||
| path: Routes.ProfileMcp, | |||
| component: `@/pages${Routes.ProfileMcp}`, | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -3,39 +3,66 @@ import registerServer from '@/utils/register-server'; | |||
| import request from '@/utils/request'; | |||
| const { | |||
| getMcpServerList, | |||
| getMultipleMcpServers, | |||
| listMcpServer, | |||
| createMcpServer, | |||
| updateMcpServer, | |||
| deleteMcpServer, | |||
| getMcpServer, | |||
| importMcpServer, | |||
| exportMcpServer, | |||
| listMcpServerTools, | |||
| testMcpServerTool, | |||
| cacheMcpServerTool, | |||
| testMcpServer, | |||
| } = api; | |||
| const methods = { | |||
| get_list: { | |||
| url: getMcpServerList, | |||
| method: 'get', | |||
| list: { | |||
| url: listMcpServer, | |||
| method: 'post', | |||
| }, | |||
| get_multiple: { | |||
| url: getMultipleMcpServers, | |||
| get: { | |||
| url: getMcpServer, | |||
| method: 'post', | |||
| }, | |||
| add: { | |||
| create: { | |||
| url: createMcpServer, | |||
| method: 'post' | |||
| method: 'post', | |||
| }, | |||
| update: { | |||
| url: updateMcpServer, | |||
| method: 'post' | |||
| method: 'post', | |||
| }, | |||
| rm: { | |||
| delete: { | |||
| url: deleteMcpServer, | |||
| method: 'post' | |||
| method: 'post', | |||
| }, | |||
| import: { | |||
| url: importMcpServer, | |||
| method: 'post', | |||
| }, | |||
| export: { | |||
| url: exportMcpServer, | |||
| method: 'post', | |||
| }, | |||
| listTools: { | |||
| url: listMcpServerTools, | |||
| method: 'get', | |||
| }, | |||
| testTool: { | |||
| url: testMcpServerTool, | |||
| method: 'post', | |||
| }, | |||
| cacheTool: { | |||
| url: cacheMcpServerTool, | |||
| method: 'post', | |||
| }, | |||
| test: { | |||
| url: testMcpServer, | |||
| method: 'post', | |||
| }, | |||
| } as const; | |||
| const mcpServerService = registerServer<keyof typeof methods>(methods, request); | |||
| export const getMcpServer = (serverId: string) => | |||
| request.get(api.getMcpServer(serverId)); | |||
| export default mcpServerService; | |||
| @@ -148,10 +148,15 @@ export default { | |||
| trace: `${api_host}/canvas/trace`, | |||
| // mcp server | |||
| getMcpServerList: `${api_host}/mcp_server/list`, | |||
| getMultipleMcpServers: `${api_host}/mcp_server/get_multiple`, | |||
| getMcpServer: (serverId: string) => `${api_host}/mcp_server/get/${serverId}`, | |||
| listMcpServer: `${api_host}/mcp_server/list`, | |||
| getMcpServer: `${api_host}/mcp_server/detail`, | |||
| createMcpServer: `${api_host}/mcp_server/create`, | |||
| updateMcpServer: `${api_host}/mcp_server/update`, | |||
| deleteMcpServer: `${api_host}/mcp_server/rm`, | |||
| importMcpServer: `${api_host}/mcp_server/import`, | |||
| exportMcpServer: `${api_host}/mcp_server/export`, | |||
| listMcpServerTools: `${api_host}/mcp_server/list_tools`, | |||
| testMcpServerTool: `${api_host}/mcp_server/test_tool`, | |||
| cacheMcpServerTool: `${api_host}/mcp_server/cache_tools`, | |||
| testMcpServer: `${api_host}/mcp_server/test_mcp`, | |||
| }; | |||