From dce9b0af7a8928eff7591e29abc7080b3450f457 Mon Sep 17 00:00:00 2001 From: z30057876 Date: Tue, 25 Nov 2025 20:48:29 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=92=8C=E6=A0=B7?= =?UTF-8?q?=E4=BE=8B=E4=B8=AD=E7=A7=BB=E9=99=A4icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/prompts/call/suggest.zh.md | 2 +- .../mcp/template/test_mcp/config.json | 1 - design/services/mcp_service.md | 122 +----------------- design/services/service.md | 4 - 4 files changed, 4 insertions(+), 125 deletions(-) diff --git a/data/prompts/call/suggest.zh.md b/data/prompts/call/suggest.zh.md index 2dac62da9..f18160ced 100644 --- a/data/prompts/call/suggest.zh.md +++ b/data/prompts/call/suggest.zh.md @@ -61,7 +61,7 @@ --- -{% if history or generated %} +{% if history|length > 0 or generated|length > 0 %} **已存在的问题:** {% for question in history -%} diff --git a/data/semantics/mcp/template/test_mcp/config.json b/data/semantics/mcp/template/test_mcp/config.json index 51c8c0a73..9c66598ab 100644 --- a/data/semantics/mcp/template/test_mcp/config.json +++ b/data/semantics/mcp/template/test_mcp/config.json @@ -7,7 +7,6 @@ "tavily-mcp": { "type": "stdio", "autoInstall": true, - "iconPath": "", "command": "npx", "args": [ "-y", diff --git a/design/services/mcp_service.md b/design/services/mcp_service.md index cb4f73a06..4eb1fd2b0 100644 --- a/design/services/mcp_service.md +++ b/design/services/mcp_service.md @@ -26,7 +26,6 @@ SSE(Server-Sent Events)和STDIO(标准输入输出), ```json { "mcpserviceId": "filesystem-service", - "icon": "/static/mcp/filesystem-service.png", "name": "文件系统服务", "description": "提供文件读写和目录操作能力", "author": "admin@example.com", @@ -42,7 +41,6 @@ SSE(Server-Sent Events)和STDIO(标准输入输出), ```json { "serviceId": "filesystem-service", - "icon": "/static/mcp/filesystem-service.png", "name": "文件系统服务", "description": "提供文件读写和目录操作能力", "overview": "这是一个功能强大的文件系统服务,支持常见的文件操作,包括读取、写入、删除和目录管理。", @@ -95,7 +93,6 @@ SSE(Server-Sent Events)和STDIO(标准输入输出), ```json { "serviceId": "database-connector", - "icon": "/static/mcp/database-connector.png", "name": "数据库连接器", "description": "提供数据库查询和操作能力", "overview": "支持MySQL、PostgreSQL等主流数据库的连接和查询", @@ -144,7 +141,6 @@ SSE(Server-Sent Events)和STDIO(标准输入输出), "services": [ { "mcpserviceId": "filesystem-service", - "icon": "/static/mcp/filesystem-service.png", "name": "文件系统服务", "description": "提供文件读写和目录操作能力", "author": "admin@example.com", @@ -153,7 +149,6 @@ SSE(Server-Sent Events)和STDIO(标准输入输出), }, { "mcpserviceId": "database-connector", - "icon": "/static/mcp/database-connector.png", "name": "数据库连接器", "description": "提供数据库查询和操作能力", "author": "admin@example.com", @@ -200,7 +195,6 @@ GET /api/mcp?searchType=NAME&keyword=文件&page=1&isActive=true "services": [ { "mcpserviceId": "filesystem-service", - "icon": "/static/mcp/filesystem-service.png", "name": "文件系统服务", "description": "提供文件读写和目录操作能力", "author": "admin@example.com", @@ -409,65 +403,7 @@ DELETE /api/admin/mcp/filesystem-service } ``` -### 6. 上传服务图标 - -**端点**: `POST /api/admin/mcp/icon` - -**权限**: 需要管理员权限 - -**请求参数**: - -- `serviceId` (string, 路径参数): 服务ID -- `icon` (UploadFile, 表单数据): 图标文件 - -**请求示例**: - -```http -POST /api/admin/mcp/icon?serviceId=filesystem-service -Content-Type: multipart/form-data - ---boundary -Content-Disposition: form-data; name="icon"; filename="icon.png" -Content-Type: image/png - -[binary data] ---boundary-- -``` - -**响应示例**: - -```json -{ - "code": 200, - "message": "OK", - "result": { - "serviceId": "filesystem-service", - "url": "/static/mcp/filesystem-service.png" - } -} -``` - -**错误响应(文件大小超限)**: - -```json -{ - "code": 400, - "message": "图标文件为空或超过1MB", - "result": {} -} -``` - -**错误响应(服务不存在)**: - -```json -{ - "code": 403, - "message": "MCP服务未找到: filesystem-service", - "result": {} -} -``` - -### 7. 激活/取消激活MCP服务 +### 6. 激活/取消激活MCP服务 **端点**: `POST /api/mcp/{mcpId}` @@ -794,16 +730,6 @@ stateDiagram-v2 - **返回值**: 存在记录返回True,否则返回False - **使用场景**: 服务列表展示时标注激活状态,权限验证 -### get_icon_path - -获取MCP服务图标的访问路径。 - -- **功能描述**: 生成服务图标的URL路径 -- **查找逻辑**: 检查图标存储目录下是否存在以服务ID命名的PNG文件 -- **路径格式**: `/static/mcp/{mcp_id}.png` -- **返回值**: 图标存在返回完整路径,否则返回空字符串 -- **注意事项**: 仅检查PNG格式图标 - ### get_service_status 获取MCP服务的当前安装状态。 @@ -822,7 +748,7 @@ stateDiagram-v2 - **执行步骤**: 1. 调用内部搜索方法获取数据库记录 2. 遍历每条记录并构建MCPServiceCardItem对象 - 3. 为每个服务加载图标、激活状态和安装状态 + 3. 为每个服务加载激活状态和安装状态 - **过滤维度**: - **搜索类型**: 全部字段、名称、描述、作者 - **关键字**: 模糊匹配相应字段 @@ -846,7 +772,7 @@ stateDiagram-v2 - **功能描述**: 从配置文件加载服务的详细配置 - **执行步骤**: 调用MCPLoader的get_config方法读取JSON配置文件 -- **返回数据**: MCPServerConfig对象和图标路径的元组 +- **返回数据**: MCPServerConfig对象 - **配置类型**: 根据mcpType字段可能是SSEConfig或StdioConfig - **使用场景**: 服务编辑、服务安装时获取配置 @@ -966,22 +892,6 @@ stateDiagram-v2 - **正则表达式**: `r'[\\\/:*?"<>|]'` - **使用场景**: 创建服务时处理用户输入的名称,确保可以作为文件名使用 -### save_mcp_icon - -上传并保存MCP服务的图标文件。 - -- **功能描述**: 处理图标上传、验证和存储 -- **执行步骤**: - 1. 检查MIME类型是否在允许列表中 - 2. 使用PIL打开图像并转换为RGB模式 - 3. 调整图像尺寸为64x64像素 - 4. 压缩并保存为PNG格式 -- **验证规则**: - - 文件大小: 不超过1MB - - 格式类型: 必须在ALLOWED_ICON_MIME_TYPES中 -- **存储路径**: `{MCP_ICON_PATH}/{mcp_id}.png` -- **返回值**: 图标的访问URL路径 - ### is_user_actived 判断用户是否已激活指定MCP服务(与is_active功能相同)。 @@ -1131,7 +1041,6 @@ stateDiagram-v2 - 更新MCP服务配置 - 删除MCP服务 - 安装/卸载服务 -- 上传服务图标 - 获取服务详情(含编辑模式) #### 作者权限 @@ -1179,22 +1088,6 @@ stateDiagram-v2 - 格式:`{原名称}-{6位hex}` - 确保服务名称在系统中的唯一性 -### 图标管理规则 - -#### 文件限制 - -- 文件大小:不超过1MB -- 文件格式:仅支持ALLOWED_ICON_MIME_TYPES中的MIME类型 -- 图片尺寸:自动调整为64x64像素 -- 存储格式:统一转换为PNG格式 - -#### 存储策略 - -- 文件名:使用服务ID作为文件名 -- 存储路径:`{ICON_PATH}/mcp/{mcp_id}.png` -- 访问路径:`/static/mcp/{mcp_id}.png` -- 覆盖策略:上传新图标会覆盖旧图标 - --- ## 配置类型说明 @@ -1302,11 +1195,6 @@ Server-Sent Events类型的MCP服务通过HTTP连接实现: │ ├── {mcp_id}.json # 用户专属配置 │ └── ... └── ... - -{ICON_PATH}/ -└── mcp/ # MCP图标目录 - ├── {mcp_id}.png # 服务图标文件 - └── ... ``` ### 目录说明 @@ -1319,10 +1207,6 @@ Server-Sent Events类型的MCP服务通过HTTP连接实现: - 每个用户一个子目录 - 配置文件包含合并后的环境变量 -- **mcp图标目录**: 统一存储所有MCP服务的图标文件 - - 64x64像素的PNG格式 - - 文件名为服务ID - --- ## 与其他模块的交互 diff --git a/design/services/service.md b/design/services/service.md index 2a413c3ca..77f7d6932 100644 --- a/design/services/service.md +++ b/design/services/service.md @@ -23,7 +23,6 @@ ```json { "serviceId": "550e8400-e29b-41d4-a716-446655440000", - "icon": "", "name": "天气查询服务", "description": "提供实时天气查询和预报功能", "author": "user@example.com", @@ -118,7 +117,6 @@ "services": [ { "serviceId": "550e8400-e29b-41d4-a716-446655440000", - "icon": "", "name": "天气查询服务", "description": "提供实时天气查询和预报功能", "author": "user@example.com", @@ -126,7 +124,6 @@ }, { "serviceId": "660e8400-e29b-41d4-a716-446655440001", - "icon": "", "name": "地图导航服务", "description": "提供地图搜索和路线规划功能", "author": "admin@example.com", @@ -216,7 +213,6 @@ GET /api/service?createdByMe=false&favorited=true&searchType=NAME&keyword=天气 "services": [ { "serviceId": "550e8400-e29b-41d4-a716-446655440000", - "icon": "", "name": "天气查询服务", "description": "提供实时天气查询和预报功能", "author": "user@example.com", -- Gitee From db43a2690d822411306aa8968ff35b3db5eebdff Mon Sep 17 00:00:00 2001 From: z30057876 Date: Tue, 25 Nov 2025 20:49:03 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E7=A8=B3=E6=AD=A5=E5=8E=BB=E9=99=A4OIDC?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/oidc.py | 8 +- apps/common/oidc_provider/__init__.py | 4 - apps/common/oidc_provider/authelia.py | 160 ------------------------- apps/common/oidc_provider/openeuler.py | 120 ------------------- 4 files changed, 2 insertions(+), 290 deletions(-) delete mode 100644 apps/common/oidc_provider/authelia.py delete mode 100644 apps/common/oidc_provider/openeuler.py diff --git a/apps/common/oidc.py b/apps/common/oidc.py index 373170141..f0a5692ec 100644 --- a/apps/common/oidc.py +++ b/apps/common/oidc.py @@ -10,7 +10,7 @@ from apps.constants import OIDC_ACCESS_TOKEN_EXPIRE_TIME, OIDC_REFRESH_TOKEN_EXP from apps.models import Session, SessionType from .config import config -from .oidc_provider import AutheliaOIDCProvider, AuthhubOIDCProvider, OpenEulerOIDCProvider +from .oidc_provider import AuthhubOIDCProvider logger = logging.getLogger(__name__) @@ -20,12 +20,8 @@ class OIDCProvider: def __init__(self) -> None: """初始化OIDC Provider""" - if config.login.provider == "openeuler": - self.provider = OpenEulerOIDCProvider() - elif config.login.provider == "authhub": + if config.login.provider == "authhub": self.provider = AuthhubOIDCProvider() - elif config.login.provider == "authelia": - self.provider = AutheliaOIDCProvider() else: err = f"[OIDC] 未知OIDC提供商: {config.login.provider}" logger.error(err) diff --git a/apps/common/oidc_provider/__init__.py b/apps/common/oidc_provider/__init__.py index cde9c1661..cdee176e1 100644 --- a/apps/common/oidc_provider/__init__.py +++ b/apps/common/oidc_provider/__init__.py @@ -1,14 +1,10 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """OIDC Provider""" -from .authelia import AutheliaOIDCProvider from .authhub import AuthhubOIDCProvider from .base import OIDCProviderBase -from .openeuler import OpenEulerOIDCProvider __all__ = [ - "AutheliaOIDCProvider", "AuthhubOIDCProvider", "OIDCProviderBase", - "OpenEulerOIDCProvider", ] diff --git a/apps/common/oidc_provider/authelia.py b/apps/common/oidc_provider/authelia.py deleted file mode 100644 index e57630ab4..000000000 --- a/apps/common/oidc_provider/authelia.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -"""Authelia OIDC Provider""" - -import logging -import secrets -from typing import Any - -import httpx -from fastapi import status - -from apps.common.config import config -from apps.common.oidc_provider.base import OIDCProviderBase -from apps.schemas.config import OIDCConfig - -logger = logging.getLogger(__name__) - - -class AutheliaOIDCProvider(OIDCProviderBase): - """Authelia OIDC Provider""" - - @classmethod - def _get_login_config(cls) -> OIDCConfig: - """获取并验证登录配置""" - login_config = config.login.settings - if not isinstance(login_config, OIDCConfig): - err = "OpenEuler OIDC配置错误" - raise TypeError(err) - return login_config - - @classmethod - async def get_oidc_token(cls, code: str) -> dict[str, Any]: - """获取Authelia OIDC Token""" - login_config = cls._get_login_config() - - data = { - "client_id": login_config.app_id, - "client_secret": login_config.app_secret, - "redirect_uri": login_config.login_api, - "grant_type": "authorization_code", - "code": code, - } - headers = { - "Content-Type": "application/x-www-form-urlencoded", - } - url = await cls.get_access_token_url() - result = None - async with httpx.AsyncClient(verify=False) as client: # noqa: S501 - resp = await client.post( - url, - headers=headers, - data=data, - timeout=10, - ) - if resp.status_code != status.HTTP_200_OK: - err = f"[Authelia] 获取OIDC Token失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - logger.info("[Authelia] 获取OIDC Token成功: %s", resp.text) - result = resp.json() - return { - "access_token": result["access_token"], - "refresh_token": result.get("refresh_token", ""), - } - - @classmethod - async def get_oidc_user(cls, access_token: str) -> dict[str, Any]: - """获取Authelia OIDC用户""" - login_config = cls._get_login_config() - - if not access_token: - err = "Access token is empty." - raise RuntimeError(err) - headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - } - url = login_config.host.rstrip("/") + "/api/oidc/userinfo" - result = None - async with httpx.AsyncClient(verify=False) as client: # noqa: S501 - resp = await client.get( - url, - headers=headers, - timeout=10, - ) - if resp.status_code != status.HTTP_200_OK: - err = f"[Authelia] 获取用户信息失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - logger.info("[Authelia] 获取用户信息成功: %s", resp.text) - result = resp.json() - - return { - "user_sub": result.get("sub", result.get("preferred_username", "")), - "user_name": result.get("name", result.get("preferred_username", result.get("nickname", ""))), - } - - @classmethod - async def get_login_status(cls, cookie: dict[str, str]) -> dict[str, Any]: - """检查登录状态;Authelia通过session cookie检查""" - login_config = cls._get_login_config() - - headers = { - "Content-Type": "application/json", - } - url = login_config.host.rstrip("/") + "/api/user/info" - async with httpx.AsyncClient(verify=False) as client: # noqa: S501 - resp = await client.get( - url, - headers=headers, - cookies=cookie, - timeout=10, - ) - if resp.status_code != status.HTTP_200_OK: - err = f"[Authelia] 获取登录状态失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - - # Authelia 返回用户信息表示已登录 - return { - "access_token": "", - "refresh_token": "", - } - - @classmethod - async def oidc_logout(cls, cookie: dict[str, str]) -> None: - """触发OIDC的登出""" - login_config = cls._get_login_config() - - headers = { - "Content-Type": "application/json", - } - url = login_config.host.rstrip("/") + "/api/logout" - async with httpx.AsyncClient(verify=False) as client: # noqa: S501 - resp = await client.post( - url, - headers=headers, - cookies=cookie, - timeout=10, - ) - # Authelia登出成功通常返回200或302重定向 - if resp.status_code not in [status.HTTP_200_OK, status.HTTP_302_FOUND]: - err = f"[Authelia] 登出失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - - @classmethod - async def get_redirect_url(cls) -> str: - """获取Authelia OIDC 重定向URL""" - login_config = cls._get_login_config() - - # 生成随机的 state 参数以确保安全性和唯一性 - state = secrets.token_urlsafe(32) - return (f"{login_config.host.rstrip('/')}/api/oidc/authorization?" - f"client_id={login_config.app_id}&" - f"response_type=code&" - f"scope=openid profile email&" - f"redirect_uri={login_config.login_api}&" - f"state={state}") - - @classmethod - async def get_access_token_url(cls) -> str: - """获取Authelia OIDC 访问Token URL""" - login_config = cls._get_login_config() - return login_config.host.rstrip("/") + "/api/oidc/token" diff --git a/apps/common/oidc_provider/openeuler.py b/apps/common/oidc_provider/openeuler.py deleted file mode 100644 index 8adfe09f7..000000000 --- a/apps/common/oidc_provider/openeuler.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -"""OpenEuler OIDC Provider""" - -import logging -from typing import Any - -import httpx -from fastapi import status - -from apps.common.config import config -from apps.schemas.config import OIDCConfig - -from .base import OIDCProviderBase - -logger = logging.getLogger(__name__) - - -class OpenEulerOIDCProvider(OIDCProviderBase): - """OpenEuler OIDC Provider""" - - @classmethod - def _get_login_config(cls) -> OIDCConfig: - """获取并验证登录配置""" - login_config = config.login.settings - if not isinstance(login_config, OIDCConfig): - err = "OpenEuler OIDC配置错误" - raise TypeError(err) - return login_config - - - @classmethod - async def get_oidc_token(cls, code: str) -> dict[str, Any]: - """获取OIDC Token""" - login_config = cls._get_login_config() - - data = { - "client_id": login_config.app_id, - "client_secret": login_config.app_secret, - "redirect_uri": login_config.login_api, - "grant_type": "authorization_code", - "code": code, - } - url = await cls.get_access_token_url() - - async with httpx.AsyncClient() as client: - resp = await client.post( - url, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - }, - data=data, - timeout=10.0, - ) - if resp.status_code != status.HTTP_200_OK: - err = f"[OpenEuler] 获取OIDC Token失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - logger.info("[OpenEuler] 获取OIDC Token成功: %s", resp.text) - result = resp.json() - - return { - "access_token": result["access_token"], - "refresh_token": result["refresh_token"], - } - - - @classmethod - async def get_oidc_user(cls, access_token: str) -> dict: - """获取OIDC用户""" - login_config = cls._get_login_config() - - if not access_token: - err = "Access token is empty." - raise RuntimeError(err) - url = login_config.host_inner.rstrip("/") + "/oneid/oidc/user" - - async with httpx.AsyncClient() as client: - resp = await client.get( - url, - headers={ - "Authorization": access_token, - }, - timeout=10.0, - ) - if resp.status_code != status.HTTP_200_OK: - err = f"[OpenEuler] 获取OIDC用户失败: {resp.status_code},完整输出: {resp.text}" - raise RuntimeError(err) - logger.info("[OpenEuler] 获取OIDC用户成功: %s", resp.text) - result = resp.json() - - return { - "user_sub": result["sub"], - } - - - @classmethod - async def get_login_status(cls, _cookie: dict[str, str]) -> dict[str, Any]: - """检查登录状态""" - return {} - - - @classmethod - async def oidc_logout(cls, _cookie: dict[str, str]) -> None: - """触发OIDC的登出""" - - - @classmethod - async def get_redirect_url(cls) -> str: - """获取OpenEuler OIDC 重定向URL""" - login_config = cls._get_login_config() - return (f"{login_config.host.rstrip('/')}/oneid/oidc/authorize" - f"?client_id={login_config.app_id}" - f"&response_type=code&access_type=offline&redirect_uri={login_config.login_api}" - "&scope=openid+profile+email+phone+offline_access") - - - @classmethod - async def get_access_token_url(cls) -> str: - """获取OpenEuler OIDC 访问Token URL""" - login_config = cls._get_login_config() - return login_config.host_inner.rstrip("/") + "/oneid/oidc/token" -- Gitee From 8620928e69a86a2a1c0a71e771c96c79f53deb33 Mon Sep 17 00:00:00 2001 From: z30057876 Date: Tue, 25 Nov 2025 20:49:31 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=8E=BB=E9=99=A4icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/models/app.py | 2 -- apps/models/service.py | 2 -- apps/models/task.py | 2 +- apps/routers/mcp_service.py | 61 ++----------------------------------- 4 files changed, 4 insertions(+), 63 deletions(-) diff --git a/apps/models/app.py b/apps/models/app.py index a2312c6b7..2b60d89b3 100644 --- a/apps/models/app.py +++ b/apps/models/app.py @@ -50,8 +50,6 @@ class App(Base): nullable=False, ) """应用更新时间""" - icon: Mapped[str] = mapped_column(String(255), default="", nullable=False) - """应用图标路径""" isPublished: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) # noqa: N815 """是否发布""" permission: Mapped[PermissionType] = mapped_column( diff --git a/apps/models/service.py b/apps/models/service.py index 60d1d711a..f347bad48 100644 --- a/apps/models/service.py +++ b/apps/models/service.py @@ -31,8 +31,6 @@ class Service(Base): nullable=False, ) """插件更新时间""" - iconPath: Mapped[str] = mapped_column(String(255), default="", nullable=False) # noqa: N815 - """插件图标路径""" permission: Mapped[PermissionType] = mapped_column( Enum(PermissionType), default=PermissionType.PUBLIC, nullable=False, ) diff --git a/apps/models/task.py b/apps/models/task.py index 4c5c65876..28ff783bd 100644 --- a/apps/models/task.py +++ b/apps/models/task.py @@ -126,7 +126,7 @@ class ExecutorCheckpoint(Base): __tablename__ = "framework_executor_checkpoint" # 执行器级数据 - taskId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("framework_task.id"), nullable=False) # noqa: N815 + taskId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) # noqa: N815 """任务ID""" appId: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True) # noqa: N815 """应用ID""" diff --git a/apps/routers/mcp_service.py b/apps/routers/mcp_service.py index e77910ade..f6f021c62 100644 --- a/apps/routers/mcp_service.py +++ b/apps/routers/mcp_service.py @@ -4,7 +4,7 @@ import logging from typing import Annotated -from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request, UploadFile, status +from fastapi import APIRouter, Depends, Path, Query, Request, status from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse @@ -22,8 +22,6 @@ from apps.schemas.mcp_service import ( GetMCPServiceListRsp, UpdateMCPServiceMsg, UpdateMCPServiceRsp, - UploadMCPServiceIconMsg, - UploadMCPServiceIconRsp, ) from apps.schemas.response_data import ResponseData from apps.services.mcp_service import MCPServiceManager @@ -194,7 +192,7 @@ async def get_service_detail( # 获取MCP服务详情 try: data = await MCPServiceManager.get_mcp_service(service_id) - config, icon = await MCPServiceManager.get_mcp_config(service_id) + config = await MCPServiceManager.get_mcp_config(service_id) except Exception as e: err = f"[MCPService] 获取MCP服务API失败: {e}" _logger.exception(err) @@ -209,7 +207,7 @@ async def get_service_detail( ), ) - if data is None or config is None or icon is None: + if data is None or config is None: return JSONResponse( status_code=status.HTTP_404_NOT_FOUND, content=jsonable_encoder( @@ -225,7 +223,6 @@ async def get_service_detail( # 组装编辑所需信息 detail = EditMCPServiceMsg( serviceId=service_id, - icon=icon, name=data.name, description=data.description, overview=config.overview, @@ -238,7 +235,6 @@ async def get_service_detail( tools = await MCPServiceManager.get_mcp_tools(service_id) detail = GetMCPServiceDetailMsg( serviceId=service_id, - icon=icon, name=data.name, description=data.description, overview=config.overview, @@ -288,57 +284,6 @@ async def delete_service(serviceId: Annotated[str, Path()]) -> JSONResponse: # ) -@admin_router.post("/icon", response_model=UpdateMCPServiceRsp) -async def update_mcp_icon( - serviceId: Annotated[str, Path()], # noqa: N803 - icon: UploadFile, -) -> JSONResponse: - """更新MCP服务图标""" - # 检查当前MCP是否存在 - try: - await MCPServiceManager.get_mcp_service(serviceId) - except Exception as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"MCP服务未找到: {e!s}") from e - - # 判断文件的size - if not icon.size or icon.size == 0 or icon.size > 1024 * 1024 * 1: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=jsonable_encoder( - ResponseData( - code=status.HTTP_400_BAD_REQUEST, - message="图标文件为空或超过1MB", - result={}, - ).model_dump(exclude_none=True, by_alias=True), - ), - ) - try: - url = await MCPServiceManager.save_mcp_icon(serviceId, icon) - except Exception as e: - err = f"[MCPServiceManager] 更新MCP服务图标失败: {e}" - _logger.exception(err) - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=jsonable_encoder( - ResponseData( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message=err, - result={}, - ).model_dump(exclude_none=True, by_alias=True), - ), - ) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder( - UploadMCPServiceIconRsp( - code=status.HTTP_200_OK, - message="OK", - result=UploadMCPServiceIconMsg(serviceId=serviceId, url=url), - ).model_dump(exclude_none=True, by_alias=True), - ), - ) - - @router.post("/{mcpId}", response_model=ActiveMCPServiceRsp) async def active_or_deactivate_mcp_service( request: Request, -- Gitee From 1b537a47ec64d5a57e9b72dbbf834c57c85730fc Mon Sep 17 00:00:00 2001 From: z30057876 Date: Tue, 25 Nov 2025 20:54:43 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=8E=BB=E9=99=A4icon=EF=BC=883=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/constants.py | 13 ----------- apps/services/mcp_service.py | 45 ++---------------------------------- apps/services/record.py | 1 - apps/services/service.py | 3 --- 4 files changed, 2 insertions(+), 60 deletions(-) diff --git a/apps/constants.py b/apps/constants.py index 4748a598a..8538bce92 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -27,23 +27,10 @@ JSON_GEN_MAX_TRIAL = 3 APP_DEFAULT_HISTORY_LEN = 3 # 插件中心每页卡片数量 SERVICE_PAGE_SIZE = 16 -# 图标允许的MIME类型 -ALLOWED_ICON_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/avif", - "image/heic", - "image/heif", - "image/webp", - "image/bmp", - "image/tiff", -] # MCP路径 MCP_PATH = Path(config.deploy.data_dir) / "semantics" / "mcp" # 项目路径 PROJ_PATH = Path(__file__).parent.parent -# 图标存储位置 -ICON_PATH = PROJ_PATH / "static" / "icons" # MCP Agent 最大重试次数 AGENT_MAX_RETRY_TIMES = 3 # MCP Agent 最大步骤数 diff --git a/apps/services/mcp_service.py b/apps/services/mcp_service.py index 7a945798d..648fba75d 100644 --- a/apps/services/mcp_service.py +++ b/apps/services/mcp_service.py @@ -6,15 +6,10 @@ import re import uuid from typing import Any -import magic -from fastapi import UploadFile -from PIL import Image from sqlalchemy import and_, delete, or_, select from apps.common.postgres import postgres from apps.constants import ( - ALLOWED_ICON_MIME_TYPES, - ICON_PATH, MCP_PATH, SERVICE_PAGE_SIZE, ) @@ -39,7 +34,6 @@ from apps.schemas.mcp_service import MCPServiceCardItem from apps.services.user import UserManager logger = logging.getLogger(__name__) -MCP_ICON_PATH = ICON_PATH / "mcp" class MCPServiceManager: @@ -57,15 +51,6 @@ class MCPServiceManager: ))).one_or_none() return bool(mcp_info) - - @staticmethod - async def get_icon_path(mcp_id: str) -> str: - """获取MCP服务图标路径""" - if (MCP_ICON_PATH / f"{mcp_id}.png").exists(): - return f"/static/mcp/{mcp_id}.png" - return "" - - @staticmethod async def get_service_status(mcp_id: str) -> MCPInstallStatus: """获取MCP服务状态""" @@ -97,7 +82,6 @@ class MCPServiceManager: return [ MCPServiceCardItem( mcpserviceId=item.id, - icon=await MCPServiceManager.get_icon_path(item.id), name=item.name, description=item.description, author=author_names.get(item.authorId, item.authorId), @@ -116,11 +100,10 @@ class MCPServiceManager: @staticmethod - async def get_mcp_config(mcp_id: str) -> tuple[MCPServerConfig, str]: + async def get_mcp_config(mcp_id: str) -> MCPServerConfig: """获取MCP服务配置""" - icon_path = "" config = await MCPLoader.get_config(mcp_id) - return config, icon_path + return config @staticmethod @@ -374,30 +357,6 @@ class MCPServiceManager: invalid_chars = r'[\\\/:*?"<>|]' return re.sub(invalid_chars, "_", name) - - @staticmethod - async def save_mcp_icon( - mcp_id: str, - icon: UploadFile, - ) -> str: - """保存MCP服务图标""" - mime = magic.from_buffer(icon.file.read(), mime=True) - icon.file.seek(0) - - if mime not in ALLOWED_ICON_MIME_TYPES: - err = "[MCPServiceManager] 不支持的图标格式" - raise ValueError(err) - - image = Image.open(icon.file) - image = image.convert("RGB") - image = image.resize((64, 64), resample=Image.Resampling.LANCZOS) - if not await MCP_ICON_PATH.exists(): - await MCP_ICON_PATH.mkdir(parents=True, exist_ok=True) - image.save(MCP_ICON_PATH / f"{mcp_id}.png", format="PNG", optimize=True, compress_level=9) - - return f"/static/mcp/{mcp_id}.png" - - @staticmethod async def is_user_actived(user_id: str, mcp_id: str) -> bool: """判断用户是否激活MCP""" diff --git a/apps/services/record.py b/apps/services/record.py index baa136243..8e05259ed 100644 --- a/apps/services/record.py +++ b/apps/services/record.py @@ -93,7 +93,6 @@ class RecordManager: records = [] for pg_record in pg_records: - # Decrypt and parse the content decrypted_content = Security.decrypt(pg_record.content, pg_record.key) record_content = RecordContent.model_validate(json.loads(decrypted_content)) diff --git a/apps/services/service.py b/apps/services/service.py index 6deb8cdf4..d21f4f5a3 100644 --- a/apps/services/service.py +++ b/apps/services/service.py @@ -117,7 +117,6 @@ class ServiceCenterManager: services = [ ServiceCardItem( serviceId=service_pool.id, - icon="", name=service_pool.name, description=service_pool.description, author=author_map[service_pool.authorId], @@ -157,7 +156,6 @@ class ServiceCenterManager: services = [ ServiceCardItem( serviceId=service_pool.id, - icon="", name=service_pool.name, description=service_pool.description, author=author_map[service_pool.authorId], @@ -206,7 +204,6 @@ class ServiceCenterManager: services = [ ServiceCardItem( serviceId=service_pool.id, - icon="", name=service_pool.name, description=service_pool.description, author=author_map[service_pool.authorId], -- Gitee From 6701f623d33898e04de777128cc87fc9d5e9c662 Mon Sep 17 00:00:00 2001 From: z30057876 Date: Tue, 25 Nov 2025 20:55:04 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=8E=BB=E9=99=A4icon=EF=BC=884=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/schemas/flow.py | 1 - apps/schemas/mcp_service.py | 16 ---------------- apps/schemas/service.py | 1 - 3 files changed, 18 deletions(-) diff --git a/apps/schemas/flow.py b/apps/schemas/flow.py index 69f027525..204e58f73 100644 --- a/apps/schemas/flow.py +++ b/apps/schemas/flow.py @@ -69,7 +69,6 @@ class MetadataBase(BaseModel): type: MetadataType = Field(description="元数据类型") id: uuid.UUID = Field(description="元数据ID") - icon: str = Field(description="图标", default="") name: str = Field(description="元数据名称") description: str = Field(description="元数据描述") author: str = Field(description="创建者的用户名") diff --git a/apps/schemas/mcp_service.py b/apps/schemas/mcp_service.py index cd26578d5..e0ba691d1 100644 --- a/apps/schemas/mcp_service.py +++ b/apps/schemas/mcp_service.py @@ -15,7 +15,6 @@ class MCPServiceCardItem(BaseModel): mcpservice_id: str = Field(..., alias="mcpserviceId", description="mcp服务ID") name: str = Field(..., description="mcp服务名称") description: str = Field(..., description="mcp服务简介") - icon: str = Field(..., description="mcp服务图标") author: str = Field(..., description="mcp服务作者") is_active: bool = Field(default=False, alias="isActive", description="mcp服务是否激活") status: MCPInstallStatus = Field(default=MCPInstallStatus.INSTALLING, description="mcp服务状态") @@ -53,24 +52,10 @@ class UpdateMCPServiceRsp(ResponseData): result: UpdateMCPServiceMsg = Field(..., title="Result") -class UploadMCPServiceIconMsg(BaseModel): - """POST /api/mcp_service/icon Result数据结构""" - - service_id: str = Field(..., alias="serviceId", description="MCP服务ID") - url: str = Field(..., description="图标URL") - - -class UploadMCPServiceIconRsp(ResponseData): - """POST /api/mcp_service/icon 返回数据结构""" - - result: UploadMCPServiceIconMsg = Field(..., title="Result") - - class GetMCPServiceDetailMsg(BaseModel): """GET /api/mcp_service/{serviceId} Result数据结构""" service_id: str = Field(..., alias="serviceId", description="MCP服务ID") - icon: str = Field(description="图标", default="") name: str = Field(..., description="MCP服务名称") description: str = Field(description="MCP服务描述") overview: str = Field(description="MCP服务概述") @@ -85,7 +70,6 @@ class EditMCPServiceMsg(BaseModel): """编辑MCP服务""" service_id: str = Field(..., alias="serviceId", description="MCP服务ID") - icon: str = Field(description="图标", default="") name: str = Field(..., description="MCP服务名称") description: str = Field(description="MCP服务描述") overview: str = Field(description="MCP服务概述") diff --git a/apps/schemas/service.py b/apps/schemas/service.py index bce4e0b2c..c8886192c 100644 --- a/apps/schemas/service.py +++ b/apps/schemas/service.py @@ -29,7 +29,6 @@ class ServiceCardItem(BaseModel): service_id: uuid.UUID = Field(..., alias="serviceId", description="服务ID") name: str = Field(..., description="服务名称") description: str = Field(..., description="服务简介") - icon: str = Field(..., description="服务图标") author: str = Field(..., description="服务作者") favorited: bool = Field(..., description="是否已收藏") -- Gitee