mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-19 03:54:19 +00:00
feat: add mcp from sse on frontend
This commit is contained in:
@@ -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')}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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: '流水线',
|
||||||
|
|||||||
Reference in New Issue
Block a user