♻️ Mcp Tools Management Page Development#2771
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new /mcp-tools management experience, including centralized MCP lifecycle management (list/filter/add/update/delete/enable/stop/healthcheck) and marketplace flows (registry/community browsing and publishing), spanning frontend UI/types/services plus backend/SDK support.
Changes:
- Added MCP Tools page UI/service layer and shared TypeScript types to support local/registry/community MCP workflows.
- Updated container/pod naming in SDK Docker/Kubernetes clients to include a UUID suffix to avoid name collisions.
- Extended backend DB/service capabilities for MCP management, including new record operations and related tests.
Reviewed changes
Copilot reviewed 51 out of 51 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/types/mcpTools.ts | Adds MCP tools domain types/enums for tabs, transport, status, payloads. |
| frontend/services/mcpToolsService.ts | Adds frontend API wrappers and helpers for MCP tools management + registry/community operations. |
| frontend/app/[locale]/mcp-tools/components/AddMcpServiceRegistrySection.tsx | Implements registry “quick add” UI; includes unsupported-option warning rendering. |
| frontend/app/[locale]/mcp-tools/components/McpServiceDetailModal.tsx | Implements MCP detail/edit modal; includes tool list wiring. |
| sdk/nexent/container/docker_client.py | Appends UUID segment to generated MCP container names. |
| sdk/nexent/container/k8s_client.py | Appends UUID segment to generated MCP pod names. |
| backend/database/remote_mcp_db.py | Adds MCP management DB helpers including container-port lookup logic. |
| backend/services/mcp_container_service.py | Adds/updates container lifecycle helpers including “start existing container” path. |
| test/backend/database/test_remote_mcp_db.py | Adds tests for restoring a soft-deleted MCP record. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from backend.database.remote_mcp_db import ( | ||
| create_mcp_record, | ||
| delete_mcp_record_by_name_and_url, | ||
| restore_mcp_record_by_name_and_url, | ||
| delete_mcp_record_by_container_id, |
There was a problem hiding this comment.
restore_mcp_record_by_name_and_url is imported/used in these tests, but there is no implementation in backend.database.remote_mcp_db (searching the module returns no matches). This will cause an ImportError and fail the test suite; either implement the restore helper in remote_mcp_db.py (and export it) or remove/update the tests to match the actual API.
| """ | ||
| with get_db_session() as session: | ||
| query = session.query(McpRecord).filter( | ||
| McpRecord.container_port == container_port, |
There was a problem hiding this comment.
get_mcp_records_by_container_port claims to return enabled MCP records, but the query only filters by container_port and delete_flag. This can incorrectly report a port as “in use” even when the MCP is disabled, preventing users from enabling/starting a container on a free port. Filter by McpRecord.enabled.is_(True) (and/or status if that’s the intended definition of enabled) or adjust the docstring/consumers to match the actual behavior.
| McpRecord.container_port == container_port, | |
| McpRecord.container_port == container_port, | |
| McpRecord.enabled.is_(True), |
There was a problem hiding this comment.
那之后要是未启用的容器端口被占用又想启用不就启用不了,所以不能这么写
| async def start_existing_mcp_container(self, container_id: str) -> Dict[str, str]: | ||
| """ | ||
| Start an existing container by container ID and return runtime access fields. | ||
|
|
||
| Args: | ||
| container_id: Existing container ID or name | ||
|
|
||
| Returns: | ||
| Dictionary with container_id, mcp_url, host_port, status, container_name | ||
|
|
||
| Raises: | ||
| MCPContainerError: If startup fails or runtime info cannot be resolved | ||
| """ | ||
| try: | ||
| # SDK Docker client exposes the native docker client as `client`. | ||
| container = self.client.client.containers.get(container_id) | ||
| container.start() | ||
| container.reload() | ||
| if container.status != "running": |
There was a problem hiding this comment.
start_existing_mcp_container directly uses Docker SDK internals (self.client.client.containers.get(...).start()), but MCPContainerManager can be initialized with a Kubernetes client when IS_DEPLOYED_BY_KUBERNETES is true. In Kubernetes mode this will raise at runtime because the SDK client exposes core_v1/apps_v1 instead of client.containers. This method should either be implemented via the SDK container-client abstraction for both runtimes, or guard Kubernetes mode and raise a clear MCPContainerError (or implement the Kubernetes equivalent start/reconcile logic).
| interface McpServiceDetailModalProps { | ||
| open: boolean; | ||
| selectedService: McpServiceItem | null; | ||
| draftService: McpServiceItem | null; | ||
| tagDrafts: string[]; | ||
| tagInputValue: string; | ||
| healthCheckLoading: boolean; | ||
| healthErrorModalVisible: boolean; | ||
| healthErrorModalTitle: string; | ||
| healthErrorModalDetail: string; | ||
| loadingTools: boolean; | ||
| toolsModalVisible: boolean; | ||
| currentServerTools: any[]; | ||
| publishLoading?: boolean; |
There was a problem hiding this comment.
Prop currentServerTools is typed as any[], which removes type safety despite McpTool being available in the codebase and already used in related APIs. Prefer a concrete type (e.g., McpTool[]) so the tool list modal and callers are checked at compile time.
| <Alert | ||
| type="warning" | ||
| showIcon | ||
| title={t("mcpTools.registry.quickAddUnsupported")} |
There was a problem hiding this comment.
Ant Design Alert doesn’t support a title prop; the title text will be ignored and the warning may render blank. Use message (and optionally description) to display the translated warning content.
| title={t("mcpTools.registry.quickAddUnsupported")} | |
| message={t("mcpTools.registry.quickAddUnsupported")} |
There was a problem hiding this comment.
message已弃用,最新版的用title才对
| open={Boolean(detail.selectedService && detail.draftService)} | ||
| selectedService={detail.selectedService} | ||
| draftService={detail.draftService} | ||
| tagDrafts={detail.tagDrafts} | ||
| tagInputValue={detail.tagInputValue} | ||
| healthCheckLoading={detail.healthCheckLoading} | ||
| healthErrorModalVisible={detail.healthErrorModalVisible} | ||
| healthErrorModalTitle={detail.healthErrorModalTitle} | ||
| healthErrorModalDetail={detail.healthErrorModalDetail} | ||
| loadingTools={detail.loadingTools} | ||
| toolsModalVisible={detail.toolsModalVisible} | ||
| currentServerTools={detail.currentServerTools} | ||
| publishLoading={detail.publishLoading} | ||
| toggleLoading={isServiceToggling(detail.selectedService?.mcpId)} | ||
| setDraftService={detail.setDraftService} | ||
| setTagInputValue={detail.setTagInputValue} | ||
| addDetailTag={detail.addDetailTag} | ||
| removeTag={detail.removeTag} | ||
| handleHealthCheck={detail.handleHealthCheck} | ||
| handleViewTools={detail.handleViewTools} | ||
| handleSaveUpdates={detail.handleSaveUpdates} | ||
| closeToolsModal={detail.closeToolsModal} | ||
| handleRefreshTools={detail.handleRefreshTools} | ||
| closeHealthErrorModal={detail.closeHealthErrorModal} | ||
| onDeleteConfirm={(serviceName) => handleDeleteConfirm(detail.selectedService!.mcpId, serviceName)} | ||
| onPublishToCommunity={detail.handlePublishToCommunity} | ||
| onToggleEnable={(item) => { |
There was a problem hiding this comment.
这里传的props太多了。基本上都是detail.xx,为什么不直接传递detail对象,而要一个个属性传递进去?一看到这么多的props,就可以直接认为代码在设计上是不合理的。要仔细考虑一下
| <McpServiceCard | ||
| key={`${service.mcpId}`} | ||
| service={service} | ||
| t={t} |
There was a problem hiding this comment.
这个t={t}也是不需要传的,每个组件都可以独立的在代码里使用 const { t } = useTranslation("common"); 不需要在组件之间传递。
|
后端和sdk的代码修改都需要补充测试用例喔 |
| onClose: () => void; | ||
| }; | ||
|
|
||
| const resolveQuickAddTarget = (type?: string | null, url?: string | null): { transportType: "http" | "sse"; serverUrl: string } | null => { |
There was a problem hiding this comment.
useMcpToolsAddRegistry 内部有大量工具函数(resolveQuickAddTarget、extractPackageEnvTemplate、extractKeyValueInputs 等约 20+ 个),应该抽取到独立的 utils/ 文件:
// 当前写法(在 hook 内部)
const resolveQuickAddTarget = (...) => { ... };
const normalizeServerKey = (...) => { ... };
const extractPackageEnvTemplate = (...) => { ... };
// 应该抽离到
// utils/mcpToolsFormatters.ts
| <div className="flex flex-col gap-6"> | ||
| <div className="flex flex-col gap-2"> | ||
| <h1 className="text-3xl md:text-4xl font-semibold text-slate-900">{t("mcpTools.page.title")}</h1> | ||
| <p className="text-slate-600 text-base">{t("mcpTools.page.subtitle")}</p> |
| value: item.tag, | ||
| label: `${item.tag} (${item.count})`, | ||
| })), | ||
| ]} |
| loadingTools={detail.loadingTools} | ||
| toolsModalVisible={detail.toolsModalVisible} |
There was a problem hiding this comment.
为啥要在这里体现Tools?首页完全没有看到任何和Tools相关的信息。前端组件之间也应该存在层级结构,Tools相关的信息要放到McpServiceDetailModal里再去获取,再去加载。这就是为什么目前前端组件为什么这么臃肿:每个前端组件都管理超过自己职责范围外的数据!
| registrySearchValue={registry.registrySearchValue} | ||
| selectedRegistryService={registry.selectedRegistryService} | ||
| filteredRegistryServices={registry.filteredRegistryServices} | ||
| registryLoading={registry.registryLoading} | ||
| registryPage={registry.registryPage} | ||
| hasPrevRegistryPage={registry.hasPrevRegistryPage} | ||
| hasNextRegistryPage={registry.hasNextRegistryPage} | ||
| registryVersion={registry.registryVersion} | ||
| registryUpdatedSince={registry.registryUpdatedSince} | ||
| registryIncludeDeleted={registry.registryIncludeDeleted} | ||
| quickAddPickerVisible={registry.quickAddPickerVisible} | ||
| quickAddCandidateService={registry.quickAddCandidateService} | ||
| quickAddOptions={registry.quickAddOptions} | ||
| selectedQuickAddOptionKey={registry.selectedQuickAddOptionKey} | ||
| quickAddVariableValues={registry.quickAddVariableValues} | ||
| quickAddContainerPort={registry.quickAddContainerPort} | ||
| quickAddSubmitting={registry.quickAddSubmitting} | ||
| setRegistrySearchValue={registry.setRegistrySearchValue} | ||
| setSelectedRegistryService={registry.setSelectedRegistryService} | ||
| setRegistryVersion={registry.setRegistryVersion} | ||
| setRegistryUpdatedSince={registry.setRegistryUpdatedSince} | ||
| setRegistryIncludeDeleted={registry.setRegistryIncludeDeleted} | ||
| setSelectedQuickAddOptionKey={registry.setSelectedQuickAddOptionKey} | ||
| handleQuickAddVariableValueChange={registry.handleQuickAddVariableValueChange} | ||
| setQuickAddContainerPort={registry.setQuickAddContainerPort} | ||
| handleRegistryPrevPage={registry.handleRegistryPrevPage} | ||
| handleRegistryNextPage={registry.handleRegistryNextPage} | ||
| handleQuickAddFromRegistry={registry.handleQuickAddFromRegistry} | ||
| handleCloseQuickAddPicker={registry.handleCloseQuickAddPicker} | ||
| handleConfirmQuickAddOption={registry.handleConfirmQuickAddOption} | ||
| t={(key, params) => String(t(key, params))} |
There was a problem hiding this comment.
超过props超过10个都已经是很多了,这里恨不得30个。选中"自定义“,”社区“,”外部“的时候,每点击一个,就会展示一个新的modal,所以modal的大小都不一样,就会很奇怪。
…式的MCP服务; 添加公共市场的浏览和搜索。
- Added new service enabling and disabling messages in English and Chinese localization files. - Updated API endpoints for enabling and disabling MCP tools. - Introduced new container service addition functionality in the MCP tools service. - Refactored mcpToolsService to handle container services and improve error handling. - Updated types for MCP tools to reflect new transport types and service details. - Created a new SQL migration script to extend the mcp_record_t table for additional MCP tool attributes. - Implemented a custom hook for managing MCP tools page state and interactions. 功能亮点:增强 MCP 工具的功能与用户界面 - 在英文和中文本地化文件中添加了启用和禁用服务的提示信息。 - 更新了用于启用和禁用 MCP 工具的 API 接口。 - 在 MCP 工具服务中引入了新增容器服务的功能。 - 重构了 mcpToolsService 以处理容器服务并改进错误处理机制。 - 更新了 MCP 工具的类型,以反映新的传输类型和服务详情。 - 创建了新的 SQL 迁移脚本,用于扩展 mcp_record_t 表以支持 MCP 工具的额外属性。 - 实现了自定义钩子,用于管理 MCP 工具页面的状态和交互。
Remove the “market_name” field from the mcp_record_t SQL extension and correct the spelling error in the source code 重构代码,将market命名转为registry 移除mcp_record_t扩展sql的market_name,修正拼写错误souce
…MCPs to the community marketplace for other users to browse and discover. 添加社区市场功能,用户可以上传自己的MCP到社区市场供其它用户浏览发现。
…uring quick addition in MCP Registry; Removal of the old MCP Tools interface and uniform migration to the new interface; Caching of the search bar in external marketplaces; Update to the logic of the tool list on the Agent page, synchronized with the MCP Tools page 外部市场支持快速添加时的变量和请求头显示和填写; 去除mcp tools的旧接口,统一改为新接口; 外部市场搜索栏缓冲; 智能体页面工具列表逻辑更新,和mcp tools页面同步更新
增加来源和传输方式筛选
…s logic” to “only making calls and mapping exceptions.” Aggregation of props for frontend detail pop-ups. New MCP domain exceptions have been added; the service layer throws MCP exceptions, and the App layer uniformly maps them to HTTP status codes. /container/add 由 App 层“做业务”改为“只做调用+异常映射”。 前端详情弹窗 props 聚合。 新增 MCP 领域异常,service 层抛出MCP异常,app 层统一映射 HTTP 状态。
将tags改为数组形式存储,新增用tag筛选mcp。
…ization header are allowed.增加请求头填写限制,只允许Authorization的Bearer Token填写
…ng and collapsing descriptions, and supports descriptions of unlimited length. 支持描述markdown形式展示,支持描述展开和收起,支持无限长的描述。
tag筛选改为前端筛选,删除相关代码
f088b6e to
b9fb8c6
Compare
# Conflicts: # backend/apps/config_app.py # backend/consts/model.py # backend/database/db_models.py
| bind_probe.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) | ||
| else: | ||
| bind_probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) | ||
| bind_probe.bind(("0.0.0.0", port)) |
[Specification Details] Fix frontend build.
…iners in Docker and Kubernetes clients. [Specification Details] Modify the name of the update SQL statement and synchronize the Kubernetes init.sql file.
…iners in Docker and Kubernetes clients. [Specification Details] 1. Fix test cases.
[Specification Details] 1. Fix test cases.
修复社区市场不必要的来源显示
…ub114/nexent into mcp_tools_develop



Added the /mcp-tools management page, allowing centralized management of browsing, filtering, adding, deleting, enabling, stopping, and modifying MCPs;






Added remote marketplace and community marketplace, allowing users to browse and obtain MCPs, allowing users to upload MCPs to the community marketplace, and allowing users to manage their own published MCPs.
添加了/mcp-tools管理页面,允许集中管理mcp的浏览筛选添加删除启用停止修改;
新增远程市场和社区市场,允许用户浏览和获取mcp,允许用户上传mcp到社区市场,允许用户管理自己发布的mcp。