feat(web): add MCP market database tracking and refresh status messages

- Add i18n translations for refresh success and failure messages in English and Chinese
- Track MCP tools already stored in database with inDatabase flag in Market component
- Display "已入库" (In Database) tag alongside activation status for MCPs
- Import getTools API to fetch full tool list for database status comparison
- Add market metadata fields (source_channel, market_id, market_config_id, mcp_service_id) to tool items when adding from market
- Preserve market source information through McpServiceModal when saving tools
- Update ToolItem type to include market tracking fields in config_data
- Improve MCP card layout to properly display multiple status tags
This commit is contained in:
yujiangping
2026-03-09 15:36:49 +08:00
parent 247db844a4
commit 4c2b31f31f
6 changed files with 52 additions and 9 deletions

View File

@@ -1806,6 +1806,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
error_desc: 'API is configured but connection error',
testConnectionSuccess: 'Test Connection Successful',
refreshSuccess: 'Refresh Successful',
refreshFailed: 'Refresh Failed',
serviceEndpoint: 'Service Endpoint URL',
serviceEndpointPlaceholder: 'URL of the service endpoint',
serviceEndpointExtra: 'Complete access address of the MCP service',

View File

@@ -1803,6 +1803,8 @@ export const zh = {
error_desc: 'API 已配置但链接异常',
testConnectionSuccess: '测试连接成功',
refreshSuccess: '刷新成功',
refreshFailed: '刷新失败',
serviceEndpoint: '服务端点 URL',
serviceEndpointPlaceholder: '服务端点的 URL',
serviceEndpointExtra: 'MCP服务的完整访问地址',

View File

@@ -6,7 +6,7 @@ import InfiniteScroll from 'react-infinite-scroll-component';
import MarketConfigModal, { type MarketConfigModalRef } from './components/MarketConfigModal';
import McpServiceModal from './components/McpServiceModal';
import type { McpServiceModalRef } from './types';
import { getMarketTools, getMarketConfig, getMarketMCPs, getMarketMCPDetail, getMarketMCPsActivated } from '@/api/tools';
import { getMarketTools, getMarketConfig, getMarketMCPs, getMarketMCPDetail, getMarketMCPsActivated, getTools } from '@/api/tools';
interface MarketSource {
id: string;
name: string;
@@ -32,6 +32,7 @@ interface MarketMcp {
tags?: string[];
view_count?: number;
activated?: boolean;
inDatabase?: boolean;
locales?: {
[lang: string]: {
name: string;
@@ -131,13 +132,27 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
}
}
// 获取全量工具列表,用于标记已入库的 MCP
const allTools: any = await getTools({ tool_type: 'mcp' });
const toolsList = Array.isArray(allTools) ? allTools : [];
const res: any = await getMarketMCPs({ mcp_market_config_id: configId, page, pagesize: pageSize });
if (res?.items && Array.isArray(res.items)) {
// 标记已激活的 MCP
const mcpsWithActivated = res.items.map((item: MarketMcp) => ({
...item,
activated: activatedIds.includes(item.id)
}));
// 标记已激活和已入库的 MCP
const mcpsWithActivated = res.items.map((item: MarketMcp) => {
// 检查是否已入库market_id = sourceId, market_config_id = configId, mcp_service_id = item.id
const isInDatabase = toolsList.some((tool: any) =>
tool.config_data?.market_id === sourceId &&
tool.config_data?.market_config_id === configId &&
tool.config_data?.mcp_service_id === item.id
);
return {
...item,
activated: activatedIds.includes(item.id),
inDatabase: isInDatabase
};
});
setMcpCache(prev => ({
...prev,
@@ -212,9 +227,14 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
mcp_market_config_id: configIdMap[selectedSource],
server_id: mcp.id,
});
const source = marketSources.find(s => s.id === selectedSource);
const toolItem = {
name: detail.name,
description: detail.description,
source_channel: source?.name || '',
market_id: selectedSource,
market_config_id: configIdMap[selectedSource],
mcp_service_id: mcp.id,
config_data: {
server_url: detail.servers?.[0]?.url || '',
connection_config: {
@@ -392,8 +412,11 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
</span>
)}
</div>
<div className={`rb:flex rb:items-center ${mcp.activated ? 'rb:justify-between' : 'rb:justify-end'}`}>
{mcp.activated && <Tag color="success"></Tag>}
<div className={`rb:flex rb:items-center ${mcp.activated || mcp.inDatabase ? 'rb:justify-between' : 'rb:justify-end'}`}>
<div className="rb:flex rb:gap-2">
{mcp.activated && <Tag color="success"></Tag>}
{mcp.inDatabase && <Tag color="blue"></Tag>}
</div>
<Button type="primary" size="small" onClick={() => handleOpenMcpServiceModal(mcp)}>
+
</Button>

View File

@@ -61,7 +61,6 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
getData()
})
};
// 删除服务
const handleDeleteService = (item: ToolItem) => {
if (!item.id) {

View File

@@ -87,6 +87,10 @@ const McpServiceModal = forwardRef<McpServiceModalRef, McpServiceModalProps>(({
name, description, icon,
...(config_data ? { config: { ...config_data } } : {})
})
// 如果是从 Market 组件传来的数据(包含 market_id保存完整的 data 用于后续提交
if ((data as any).market_id) {
setEditVo(data)
}
} else {
form.resetFields();
}
@@ -116,6 +120,15 @@ const McpServiceModal = forwardRef<McpServiceModalRef, McpServiceModalProps>(({
}
}
}
// 如果是从 Market 组件传来的数据,添加市场相关字段
if ((editVo as any)?.market_id) {
(newService.config as any).source_channel = (editVo as any).source_channel;
(newService.config as any).market_id = (editVo as any).market_id;
(newService.config as any).market_config_id = (editVo as any).market_config_id;
(newService.config as any).mcp_service_id = (editVo as any).mcp_service_id;
}
const request = editVo?.id ? updateTool(editVo.id, newService) : addTool(newService)
request.then((res: any) => {
message.success(t('common.saveSuccess'));

View File

@@ -75,6 +75,10 @@ export interface ToolItem {
tool_class: string;
schema_content: string;
source_channel?: string;
market_id?: string;
market_config_id?: string;
mcp_service_id?: string;
};
status: 'available' | 'unavailable';
tags: string[];