feat: add mcp from sse on frontend

This commit is contained in:
wangcham
2025-10-13 12:51:58 +00:00
parent d65f862c36
commit 68372a4b7a
3 changed files with 104 additions and 29 deletions
+85 -29
View File
@@ -41,6 +41,9 @@ import { useTranslation } from 'react-i18next';
import { PluginV4 } from '@/app/infra/entities/plugin'; import { PluginV4 } from '@/app/infra/entities/plugin';
import { systemInfo } from '@/app/infra/http/HttpClient'; import { systemInfo } from '@/app/infra/http/HttpClient';
import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api'; import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api';
import { set } from 'lodash';
import { passiveEventSupported } from '@tanstack/react-table';
import { config } from 'process';
enum PluginInstallStatus { enum PluginInstallStatus {
WAIT_INPUT = 'wait_input', WAIT_INPUT = 'wait_input',
@@ -69,7 +72,7 @@ export default function PluginConfigPage() {
); );
const [mcpSSEHeaders,setMcpSSEHeaders] = useState('') const [mcpSSEHeaders,setMcpSSEHeaders] = useState('')
const [mcpName,setMcpName] = useState('') const [mcpName,setMcpName] = useState('')
const [mcpTimeout,setMcpTimeout] = useState('') const [mcpTimeout,setMcpTimeout] = useState(60)
const [installError, setInstallError] = useState<string | null>(null); const [installError, setInstallError] = useState<string | null>(null);
const [mcpInstallError, setMcpInstallError] = useState<string | null>(null); const [mcpInstallError, setMcpInstallError] = useState<string | null>(null);
const [githubURL, setGithubURL] = useState(''); const [githubURL, setGithubURL] = useState('');
@@ -125,6 +128,7 @@ export default function PluginConfigPage() {
} }
const [mcpGithubURL, setMcpGithubURL] = useState(''); const [mcpGithubURL, setMcpGithubURL] = useState('');
const [mcpSSEURL, setMcpSSEURL] = useState(''); const [mcpSSEURL, setMcpSSEURL] = useState('');
const [mcpSSEConfig, setMcpSSEConfig] = useState<Record<string, any> | null>(null);
const [mcpInstallConfig, setMcpInstallConfig] = useState<Record<string, any> | null>(null); const [mcpInstallConfig, setMcpInstallConfig] = useState<Record<string, any> | null>(null);
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null); const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
const mcpComponentRef = useRef<MCPComponentRef>(null); const mcpComponentRef = useRef<MCPComponentRef>(null);
@@ -134,7 +138,7 @@ export default function PluginConfigPage() {
} }
function handleMcpModalConfirm() { function handleMcpModalConfirm() {
installMcpServer(mcpGithubURL); installMcpServerFromSSE(mcpSSEConfig ?? {});
} }
function installPlugin( function installPlugin(
installSource: string, installSource: string,
@@ -316,18 +320,14 @@ export default function PluginConfigPage() {
return renderPluginConnectionErrorState(); return renderPluginConnectionErrorState();
} }
function installMcpServer(url: string, config?: Record<string, any>) { function installMcpServerFromSSE(config?: Record<string, any>) {
setMcpInstallStatus(PluginInstallStatus.INSTALLING); setMcpInstallStatus(PluginInstallStatus.INSTALLING);
// NOTE: backend currently only accepts url. If backend accepts config in future, console.log('installing mcp server from sse with config:', config);
// replace this call with: httpClient.installMCPServerFromGithub(url, config) httpClient.installMCPServerFromSSE(config ?? {})
console.log('installing mcp server with config:', config);
httpClient.installMCPServerFromGithub(url)
.then((resp) => { .then((resp) => {
const taskId = resp.task_id; const taskId = resp.task_id;
let alreadySuccess = false; let alreadySuccess = false;
console.log('taskId:', taskId); console.log('taskId:', taskId);
// 每秒拉取一次任务状态 // 每秒拉取一次任务状态
const interval = setInterval(() => { const interval = setInterval(() => {
httpClient.getAsyncTask(taskId).then((resp) => { httpClient.getAsyncTask(taskId).then((resp) => {
@@ -343,8 +343,12 @@ export default function PluginConfigPage() {
toast.success(t('mcp.installSuccess')); toast.success(t('mcp.installSuccess'));
alreadySuccess = true; alreadySuccess = true;
} }
setMcpGithubURL(''); setMcpSSEInstallModalOpen(false);
setMcpMarketInstallModalOpen(false); setMcpName('');
setMcpDescription('');
setMcpSSEURL('');
setMcpSSEHeaders('');
setMcpTimeout(60);
mcpComponentRef.current?.refreshServerList(); mcpComponentRef.current?.refreshServerList();
} }
} }
@@ -358,6 +362,48 @@ export default function PluginConfigPage() {
}); });
} }
// function installMcpServer(url: string, config?: Record<string, any>) {
// setMcpInstallStatus(PluginInstallStatus.INSTALLING);
// // NOTE: backend currently only accepts url. If backend accepts config in future,
// // replace this call with: httpClient.installMCPServerFromGithub(url, config)
// console.log('installing mcp server with config:', config);
// httpClient.installMCPServerFromGithub(url)
// .then((resp) => {
// const taskId = resp.task_id;
// let alreadySuccess = false;
// console.log('taskId:', taskId);
// // 每秒拉取一次任务状态
// const interval = setInterval(() => {
// httpClient.getAsyncTask(taskId).then((resp) => {
// console.log('task status:', resp);
// if (resp.runtime.done) {
// clearInterval(interval);
// if (resp.runtime.exception) {
// setMcpInstallError(resp.runtime.exception);
// setMcpInstallStatus(PluginInstallStatus.ERROR);
// } else {
// // success
// if (!alreadySuccess) {
// toast.success(t('mcp.installSuccess'));
// alreadySuccess = true;
// }
// setMcpGithubURL('');
// setMcpMarketInstallModalOpen(false);
// mcpComponentRef.current?.refreshServerList();
// }
// }
// });
// }, 1000);
// })
// .catch((err) => {
// console.log('error when install mcp server:', err);
// setMcpInstallError(err.message);
// setMcpInstallStatus(PluginInstallStatus.ERROR);
// });
// }
return ( return (
<div <div
className={`${styles.pageContainer} ${isDragOver ? 'bg-blue-50' : ''}`} className={`${styles.pageContainer} ${isDragOver ? 'bg-blue-50' : ''}`}
@@ -424,7 +470,9 @@ export default function PluginConfigPage() {
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setActiveTab('mcp-market'); setActiveTab('mcp-market');
// setMcpMarketInstallModalOpen(true); setMcpSSEInstallModalOpen(true);
setMcpInstallStatus(PluginInstallStatus.WAIT_INPUT);
setMcpInstallError(null);
}} }}
> >
<PlusIcon className="w-4 h-4" /> <PlusIcon className="w-4 h-4" />
@@ -586,8 +634,8 @@ export default function PluginConfigPage() {
<label className='text-sm text-muted-foreground block mb-1'> <label className='text-sm text-muted-foreground block mb-1'>
{t('mcp.name')} {t('mcp.name')}
</label> </label>
<Input <Input
placeholder='mcp.nameExplained' placeholder={t('mcp.nameExplained')}
value={mcpName} value={mcpName}
onChange={(e) => setMcpName(e.target.value)} onChange={(e) => setMcpName(e.target.value)}
className='mb-1' className='mb-1'
@@ -598,10 +646,10 @@ export default function PluginConfigPage() {
<div> <div>
<div> <div>
<label className="text-sm text-muted-foreground block mb-1"> <label className="text-sm text-muted-foreground block mb-1">
{t('mcp.description')} {t('mcp.mcpDescription')}
</label> </label>
<Input <Input
placeholder={t('mcp.descriptionExplain')} placeholder={t('mcp.descriptionExplained')}
value={mcpDescription} value={mcpDescription}
onChange={(e) => setMcpDescription(e.target.value)} onChange={(e) => setMcpDescription(e.target.value)}
className='mb-1' className='mb-1'
@@ -613,7 +661,7 @@ export default function PluginConfigPage() {
<div className="mt-4 space-y-3"> <div className="mt-4 space-y-3">
<div> <div>
<label className="text-sm text-muted-foreground block mb-1"> <label className="text-sm text-muted-foreground block mb-1">
{t('mcp.sseUrl')} {t('mcp.sseURL')}
</label> </label>
<Input <Input
placeholder={t('mcp.enterSSELink')} placeholder={t('mcp.enterSSELink')}
@@ -630,7 +678,7 @@ export default function PluginConfigPage() {
{t('mcp.headers')} {t('mcp.headers')}
</label> </label>
<Input <Input
placeholder={t('mcp.headers')} placeholder={t('mcp.headersExample')}
value={mcpSSEHeaders} value={mcpSSEHeaders}
onChange={(e) => setMcpSSEHeaders(e.target.value)} onChange={(e) => setMcpSSEHeaders(e.target.value)}
className="mb-1" className="mb-1"
@@ -645,8 +693,8 @@ export default function PluginConfigPage() {
</label> </label>
<Input <Input
placeholder={t('mcp.enterTimeout')} placeholder={t('mcp.enterTimeout')}
value={mcpTimeout || '60'} value={mcpTimeout || 60}
onChange={(e) => setMcpTimeout(e.target.value)} onChange={(e) => setMcpTimeout(Number(e.target.value))}
className="mb-1" className="mb-1"
/> />
</div> </div>
@@ -676,6 +724,10 @@ export default function PluginConfigPage() {
setMcpInstallError(null); setMcpInstallError(null);
setMcpInstallConfig(null); setMcpInstallConfig(null);
setMcpSSEURL('') setMcpSSEURL('')
setMcpName('')
setMcpTimeout(60)
setMcpDescription('')
setMcpSSEHeaders('')
}} }}
> >
{t('common.cancel')} {t('common.cancel')}
@@ -687,19 +739,23 @@ export default function PluginConfigPage() {
toast.error(t('mcp.urlRequired')); toast.error(t('mcp.urlRequired'));
return; return;
} }
if (!mcpName) ( if (!mcpName) {
toast.error(t('')) toast.error(t('mcp.nameRequired'));
) }
if (!mcpTimeout) {
toast.error(t('mcp.timeoutRequired'));
}
const configToSend = { const configToSend = {
name: mcpSSEConfig?.name,
description: mcpSSEConfig?.description,
sse_url: mcpSSEConfig?.sse_url,
sse_headers: mcpSSEConfig?.sse_headers,
timeout: Number(mcpSSEConfig?.timeout) || 60,
}; };
handleMcpModalConfirm(); handleMcpModalConfirm();
// call installer (for now installMcpServer will log config and call backend with url only) // call installer (for now installMcpServer will log config and call backend with url only)
installMcpServer(mcpGithubURL, configToSend); installMcpServerFromSSE(configToSend);
}} }}
> >
{t('common.confirm')} {t('common.confirm')}
@@ -865,7 +921,7 @@ export default function PluginConfigPage() {
handleMcpModalConfirm(); handleMcpModalConfirm();
// call installer (for now installMcpServer will log config and call backend with url only) // call installer (for now installMcpServer will log config and call backend with url only)
installMcpServer(mcpGithubURL, configToSend); // installMcpServer(mcpGithubURL, configToSend);
}} }}
> >
{t('common.confirm')} {t('common.confirm')}
+6
View File
@@ -552,6 +552,12 @@ export class BackendClient extends BaseHttpClient {
return this.post('/api/v1/mcp/install/github', { source }); return this.post('/api/v1/mcp/install/github', { source });
} }
public installMCPServerFromSSE(
source: {},
): Promise<AsyncTaskCreatedResp> {
return this.post('/api/v1/mcp/install/sse', { source });
}
// ============ System API ============ // ============ System API ============
public getSystemInfo(): Promise<ApiRespSystemInfo> { public getSystemInfo(): Promise<ApiRespSystemInfo> {
return this.get('/api/v1/system/info'); return this.get('/api/v1/system/info');
+13
View File
@@ -1,3 +1,4 @@
const zhHans = { const zhHans = {
common: { common: {
login: '登录', login: '登录',
@@ -335,6 +336,18 @@ const zhHans = {
enterGithubLink: '输入Github仓库链接', enterGithubLink: '输入Github仓库链接',
add: '添加', add: '添加',
name:'名称', name:'名称',
nameExplained:'用于区分不同的MCP服务器实例',
mcpDescription:'描述',
descriptionExplained:'简要描述这个MCP服务器的功能或用途',
sseURL:'SSE URL',
sseHeaders:'SSE Headers',
nameRequired:'名称不能为空',
sseURLRequired:'SSE URL不能为空',
enterSSELink:'输入SSE URL',
timeoutRequired:'超时时间不能为空',
headersExample:'示例: Authorization: Bearer token123',
enterTimeout:'输入超时时间,单位为毫秒',
installFromSSE:'从SSE安装',
}, },
pipelines: { pipelines: {
title: '流水线', title: '流水线',