Skip to content

Commit ee181cf

Browse files
♻️ Mcp Tools Management Page Development (#2771)
* 添加MCP管理界面,支持添加、删除、修改、启用、停止、查看MCP服务,支持用户自行添加和公共市场快速添加,目前公共市场快速添加只支持链接形式的MCP服务; 添加公共市场的浏览和搜索。 * 重构代码以符合项目规范,前端样式修改 * feat: Enhance MCP Tools functionality and UI - 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 工具页面的状态和交互。 * Rewrite the code to rename “market” to ‘registry’ 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 * Add a community marketplace feature where users can upload their own MCPs to the community marketplace for other users to browse and discover. 添加社区市场功能,用户可以上传自己的MCP到社区市场供其它用户浏览发现。 * Support for displaying and filling in variables and request headers during 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页面同步更新 * Add source and transport type method filters 增加来源和传输方式筛选 * /container/add: The App layer has been changed from “handling business 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 状态。 * Store tags as an array and add the ability to filter MCPs by tag. 将tags改为数组形式存储,新增用tag筛选mcp。 * Add restrictions on request headers; only Bearer tokens in the Authorization header are allowed.增加请求头填写限制,只允许Authorization的Bearer Token填写 * Supports displaying descriptions in Markdown format, supports expanding and collapsing descriptions, and supports descriptions of unlimited length. 支持描述markdown形式展示,支持描述展开和收起,支持无限长的描述。 * Add pagination to the community marketplace; add display of shipping methods and tags; add filtering by shipping methods and tags. 社区市场添加分页,添加传输方式和tag显示,添加传输方式和tag筛选. * Optimized the display of installation package variables on the remote market details page; Added a hyperlink entry point to the Modelscope MCP Plaza. 优化外部市场详情界面安装包变量显示; 新增魔搭mcp广场超链接入口。 * Fine-tune description style 微调描述样式 * Added container service port conflict verification and recommended port features; When quickly adding a container from the external market, a port needs to be filled in; Refactored the code of mcp_management_app and service, moving a large amount of data validation handling to the app, using Pydantic for validation, simplifying the code. 添加了容器服务端口冲突校验和推荐端口功能; 外部市场快速添加容器需要填写端口; 重构了mcp_managemeny_app和service的代码,讲大量数据检验处理移动到app中,利用Pydantic校验,简化代码。 * Data validation for the mcptools frontend form; Fix the front-end and back-end integration errors of My Posts and Community Market; Fix the display of description editing in My Posts editing and Community Market. mcptools前端表单进行数据校验; 修复我的发布和社区市场前后端对接错误; 修复我的发布编辑和社区市场的描述编辑显示。 * 禁止oci形式mcp添加 * Add MCP.so hyperlink 添加MCP.so超链接 * Optimized the frontend's handling of OCI unsupported display. Slightly optimized the backend code for OCI unsupported cases. 优化前端对oci的不支持显示。 稍微优化了后端对oci不支持的代码。 * Remove duplicate code 去除重复代码 * Optimization Recommended ports are now random Fixed container name duplication issue Abstracted front-end code port handling to reduce duplicate code Added a prompt for failed addition of services with duplicate names Loading state between each MCP card is independent and does not interfere 优化 推荐端口现在改为随机 容器重名问题修复 前端代码端口处理问题抽象,减少重复代码 增加对重复命名服务添加失败的提示 每个mcp卡片之间加载状态独立不干扰 * Fixed the issue of accompanying registry_json errors when adding to the community market, and optimized redundant code 修复社区市场添加时附带registry_json错误的问题,并优化冗余代码 * Readjust, change stdio to a container 重新调整,把stdio改成容器 * Mini Fix 小修复 * ♻️ Refactor: Refactor the remote_mcp and mcp_management services, and adapt the Agent development page to the mcp service. * 尝试优化前端代码架构,进行初步重构,优化了Props传递和hooks设计不合理的一部分问题; 删除mcp描述的markdown展示; 删除不必要的工具列表Model,改用统一Model Component。 * 二次重构,继续优化 * 前端代码样式重构,优化标题样式,去除所有圆角,修改主界面布局,去除我的发布,改为主界面导入的服务和发布的服务切换,优化搜索栏和筛选栏样式,优化卡片列表支持动态布局,优化详情弹窗显示,优化查看server.json和容器配置弹窗样式,修改标签样式,增加发布到社区的确认和修改弹窗,去除连通性校验弹窗,优化添加弹窗显示避免大小抖动,去除common.json中部分过时字段 后端去除last_sync_time字段和少量冗余代码 * 优化状态显示组件,移动端口组件 * 修复端口冲突显示bug 调整部分Types和const的代码写法 * 修改容器配置和server.json弹窗样式 修复小bug * 修复更新合并带来的部分冲突,如/list和delete 由于接口变化,修改tag筛选为前端筛选 * ♻️ Refactor: Refactor the remote_mcp and mcp_management services, and adapt the Agent development page to the mcp service. [Specification Details] 1. Modify healthcheck method. * 修改部分post接口为get * 修复输入了authorization_token但是list却不传authorization_token的问题. 修复删除Mcp不删除对应容器的问题 修复前端点击启用关闭不刷新的问题,并添加刷新工具中信息提示。 * 简化部分组件传递信息代码 * 修复我的发布详情界面滚动条问题 * 修复社区市场tag筛选项是按租户显示的问题; 社区市场传输类型筛选改为后端实现; 去除原来的http和sse,统一改为url; 修复部分代码不必要的null和可选性问题; 修复部分翻译键问题; 修复添加容器化服务的部分前端逻辑代码错误。 * 修复社区市场弹窗层级问题 * 修改发布代码逻辑 支持发布时修改config_json和url 删除healthcheck详细显示 * 整体页面色调改为绿色 * 修改主界面切换页签样式 修改主界面布局 * 修改卡片样式设计 * 改进添加和详情弹窗,缩小宽度,添加动态过渡 * 小修复 * 给发布的服务提供筛选栏 tag筛选改为前端筛选,删除相关代码 * 调整容器启用关闭以兼容k8s部署,现在会删除停止的容器,启用时重新拉取 * 修复详情界面切换服务状态信息显示不更新问题 * 修复页签字体不居中,筛选栏字体大小问题 * 前端代码组件分类 * 修复弹窗问题 * 改名 * 修复i18n缺失bug * 删除部分未使用文本 * 小修复 * 添加测试文件 * 修改sql * 更新文档 * ♻️ Refactor: Front-end style adjustment. * ♻️ Refactor: Front-end style adjustment & test cases addition. * ♻️ Refactor: Front-end style adjustment & test cases addition. [Specification Details] Fix frontend build. * ♻️ Refactor: Revert to the container naming rules for launching containers in Docker and Kubernetes clients. [Specification Details] Modify the name of the update SQL statement and synchronize the Kubernetes init.sql file. * ♻️ Refactor: Revert to the container naming rules for launching containers in Docker and Kubernetes clients. [Specification Details] 1. Fix test cases. * ♻️ Refactor: Add UUID when creating the container. [Specification Details] 1. Fix test cases. * 修复描述为空导致的搜索栏bug 修复社区市场不必要的来源显示 * 🐛 Bugfix: Fix the issue of missing fields in init.sql. * 🐛 Bugfix: ModelAddDialog.tsx rollback --------- Co-authored-by: panyehong <2655992392@qq.com> Co-authored-by: panyehong <91180085+YehongPan@users.noreply.github.com>
1 parent 1b93a61 commit ee181cf

83 files changed

Lines changed: 15095 additions & 5671 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/apps/config_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from apps.oauth_app import router as oauth_router
1818
from apps.prompt_app import router as prompt_router
1919
from apps.prompt_template_app import router as prompt_template_router
20+
from apps.mcp_management_app import router as mcp_management_router
2021
from apps.remote_mcp_app import router as remote_mcp_router
2122
from apps.skill_app import router as skill_router
2223
from apps.tenant_config_app import router as tenant_config_router
@@ -78,6 +79,7 @@ async def sync_default_prompt_template_on_startup():
7879
app.include_router(prompt_template_router)
7980
app.include_router(skill_router)
8081
app.include_router(tenant_config_router)
82+
app.include_router(mcp_management_router)
8183
app.include_router(remote_mcp_router)
8284
app.include_router(tenant_router)
8385
app.include_router(group_router)

backend/apps/mcp_management_app.py

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import logging
2+
from typing import Optional
3+
4+
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request
5+
from fastapi.responses import JSONResponse
6+
from http import HTTPStatus
7+
8+
from consts.exceptions import (
9+
MCPConnectionError,
10+
McpNotFoundError,
11+
McpValidationError,
12+
UnauthorizedError,
13+
)
14+
from consts.model import (
15+
RegistryListQuery,
16+
CommunityListRequest,
17+
CommunityPublishRequest,
18+
CommunityUpdateRequest,
19+
)
20+
from services.mcp_management_service import (
21+
list_community_mcp_services,
22+
list_community_mcp_tag_stats,
23+
list_my_community_mcp_services,
24+
list_registry_mcp_services,
25+
publish_community_mcp_service,
26+
update_community_mcp_service,
27+
delete_community_mcp_service,
28+
)
29+
from utils.auth_utils import get_current_user_info
30+
31+
router = APIRouter(prefix="/mcp-tools")
32+
logger = logging.getLogger("mcp_management_app")
33+
34+
35+
# ---------------------------------------------------------------------------
36+
# Registry Endpoints (MCP Registry - external service)
37+
# ---------------------------------------------------------------------------
38+
39+
@router.get("/registry/list")
40+
async def list_registry_mcp_services_api(
41+
query: RegistryListQuery = Depends(),
42+
authorization: Optional[str] = Header(None),
43+
http_request: Request = None,
44+
):
45+
"""
46+
List MCP services from the official MCP Registry.
47+
"""
48+
try:
49+
get_current_user_info(authorization, http_request)
50+
51+
data = await list_registry_mcp_services(
52+
search=query.search,
53+
include_deleted=query.include_deleted,
54+
updated_since=query.updated_since,
55+
version=query.version,
56+
cursor=query.cursor,
57+
limit=query.limit,
58+
)
59+
return JSONResponse(
60+
status_code=HTTPStatus.OK,
61+
content=data,
62+
)
63+
except UnauthorizedError as exc:
64+
raise HTTPException(
65+
status_code=HTTPStatus.UNAUTHORIZED,
66+
detail=str(exc),
67+
)
68+
except HTTPException:
69+
raise
70+
except Exception as exc:
71+
logger.error(f"Failed to list MCP registry services: {exc}")
72+
raise HTTPException(
73+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
74+
detail="Failed to list MCP registry services"
75+
)
76+
77+
78+
# ---------------------------------------------------------------------------
79+
# Community Endpoints
80+
# ---------------------------------------------------------------------------
81+
82+
@router.get("/community/list")
83+
async def list_community_mcp_services_api(
84+
query: CommunityListRequest = Depends(),
85+
authorization: Optional[str] = Header(None),
86+
http_request: Request = None,
87+
):
88+
"""
89+
List public community MCP services.
90+
"""
91+
try:
92+
get_current_user_info(authorization, http_request)
93+
data = await list_community_mcp_services(
94+
search=query.search,
95+
tag=query.tag,
96+
transport_type=query.transport_type,
97+
cursor=query.cursor,
98+
limit=query.limit,
99+
)
100+
return JSONResponse(
101+
status_code=HTTPStatus.OK,
102+
content={"status": "success", "data": data},
103+
)
104+
except UnauthorizedError as exc:
105+
raise HTTPException(
106+
status_code=HTTPStatus.UNAUTHORIZED,
107+
detail=str(exc),
108+
)
109+
except HTTPException:
110+
raise
111+
except Exception as exc:
112+
logger.error(f"Failed to list MCP community services: {exc}")
113+
raise HTTPException(
114+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
115+
detail="Failed to list MCP community services"
116+
)
117+
118+
119+
@router.get("/community/tags/stats")
120+
async def list_community_mcp_tag_stats_api(
121+
authorization: Optional[str] = Header(None),
122+
http_request: Request = None,
123+
):
124+
"""
125+
Get community MCP tag statistics.
126+
"""
127+
try:
128+
get_current_user_info(authorization, http_request)
129+
stats = list_community_mcp_tag_stats()
130+
return JSONResponse(
131+
status_code=HTTPStatus.OK,
132+
content={"status": "success", "data": stats},
133+
)
134+
except UnauthorizedError as exc:
135+
raise HTTPException(
136+
status_code=HTTPStatus.UNAUTHORIZED,
137+
detail=str(exc),
138+
)
139+
except HTTPException:
140+
raise
141+
except Exception as exc:
142+
logger.error(f"Failed to list community MCP tag stats: {exc}")
143+
raise HTTPException(
144+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
145+
detail="Failed to list community MCP tag stats"
146+
)
147+
148+
149+
@router.post("/community/publish")
150+
async def publish_community_mcp_service_api(
151+
payload: CommunityPublishRequest,
152+
authorization: Optional[str] = Header(None),
153+
http_request: Request = None,
154+
):
155+
"""
156+
Publish a local MCP service to the community.
157+
"""
158+
try:
159+
user_id, tenant_id, _ = get_current_user_info(authorization, http_request)
160+
community_id = await publish_community_mcp_service(
161+
tenant_id=tenant_id,
162+
user_id=user_id,
163+
mcp_id=payload.mcp_id,
164+
name=payload.name,
165+
description=payload.description,
166+
version=payload.version,
167+
tags=payload.tags,
168+
mcp_server=payload.mcp_server,
169+
config_json=payload.config_json,
170+
)
171+
return JSONResponse(
172+
status_code=HTTPStatus.OK,
173+
content={"status": "success", "data": {"community_id": community_id}},
174+
)
175+
except McpNotFoundError as exc:
176+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
177+
except McpValidationError as exc:
178+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
179+
except UnauthorizedError as exc:
180+
raise HTTPException(
181+
status_code=HTTPStatus.UNAUTHORIZED,
182+
detail=str(exc),
183+
)
184+
except HTTPException:
185+
raise
186+
except Exception as exc:
187+
logger.error(f"Failed to publish MCP community service: {exc}")
188+
raise HTTPException(
189+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
190+
detail="Failed to publish MCP community service"
191+
)
192+
193+
194+
@router.put("/community/update")
195+
async def update_community_mcp_service_api(
196+
payload: CommunityUpdateRequest,
197+
authorization: Optional[str] = Header(None),
198+
http_request: Request = None,
199+
):
200+
"""
201+
Update a community MCP service.
202+
"""
203+
try:
204+
user_id, tenant_id, _ = get_current_user_info(authorization, http_request)
205+
await update_community_mcp_service(
206+
tenant_id=tenant_id,
207+
user_id=user_id,
208+
community_id=payload.community_id,
209+
name=payload.name,
210+
description=payload.description,
211+
tags=payload.tags,
212+
version=payload.version,
213+
registry_json=payload.registry_json,
214+
)
215+
return JSONResponse(
216+
status_code=HTTPStatus.OK,
217+
content={"status": "success"},
218+
)
219+
except McpNotFoundError as exc:
220+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
221+
except McpValidationError as exc:
222+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
223+
except UnauthorizedError as exc:
224+
raise HTTPException(
225+
status_code=HTTPStatus.UNAUTHORIZED,
226+
detail=str(exc),
227+
)
228+
except HTTPException:
229+
raise
230+
except Exception as exc:
231+
logger.error(f"Failed to update MCP community service: {exc}")
232+
raise HTTPException(
233+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
234+
detail="Failed to update MCP community service"
235+
)
236+
237+
238+
@router.delete("/community/delete")
239+
async def delete_community_mcp_service_api(
240+
community_id: int = Query(gt=0),
241+
authorization: Optional[str] = Header(None),
242+
http_request: Request = None,
243+
):
244+
"""
245+
Delete a community MCP service.
246+
"""
247+
try:
248+
user_id, tenant_id, _ = get_current_user_info(authorization, http_request)
249+
await delete_community_mcp_service(
250+
tenant_id=tenant_id,
251+
user_id=user_id,
252+
community_id=community_id,
253+
)
254+
return JSONResponse(
255+
status_code=HTTPStatus.OK,
256+
content={"status": "success"},
257+
)
258+
except McpNotFoundError as exc:
259+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
260+
except UnauthorizedError as exc:
261+
raise HTTPException(
262+
status_code=HTTPStatus.UNAUTHORIZED,
263+
detail=str(exc),
264+
)
265+
except HTTPException:
266+
raise
267+
except Exception as exc:
268+
logger.error(f"Failed to delete MCP community service: {exc}")
269+
raise HTTPException(
270+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
271+
detail="Failed to delete MCP community service"
272+
)
273+
274+
275+
@router.get("/community/mine")
276+
async def list_my_community_mcp_services_api(
277+
authorization: Optional[str] = Header(None),
278+
http_request: Request = None,
279+
):
280+
"""
281+
List MCP services published by the current user to the community.
282+
"""
283+
try:
284+
_, tenant_id, _ = get_current_user_info(authorization, http_request)
285+
data = await list_my_community_mcp_services(tenant_id=tenant_id)
286+
return JSONResponse(
287+
status_code=HTTPStatus.OK,
288+
content={"status": "success", "data": data},
289+
)
290+
except UnauthorizedError as exc:
291+
raise HTTPException(
292+
status_code=HTTPStatus.UNAUTHORIZED,
293+
detail=str(exc),
294+
)
295+
except HTTPException:
296+
raise
297+
except Exception as exc:
298+
logger.error(f"Failed to list my MCP community services: {exc}")
299+
raise HTTPException(
300+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
301+
detail="Failed to list my MCP community services"
302+
)

0 commit comments

Comments
 (0)