diff --git a/api/controllers/console/app/mcp_server.py b/api/controllers/console/app/mcp_server.py index aada292a86..8c691abffb 100644 --- a/api/controllers/console/app/mcp_server.py +++ b/api/controllers/console/app/mcp_server.py @@ -53,7 +53,6 @@ class AppMCPServerController(Resource): ) db.session.add(server) db.session.commit() - return server @setup_required @@ -68,12 +67,17 @@ class AppMCPServerController(Resource): parser.add_argument("id", type=str, required=True, location="json") parser.add_argument("description", type=str, required=True, location="json") parser.add_argument("parameters", type=dict, required=True, location="json") + parser.add_argument("status", type=str, required=False, location="json") args = parser.parse_args() server = db.session.query(AppMCPServer).filter(AppMCPServer.id == args["id"]).first() if not server: raise Forbidden() server.description = args["description"] server.parameters = json.dumps(args["parameters"], ensure_ascii=False) + if args["status"]: + if args["status"] not in [status.value for status in AppMCPServerStatus]: + raise ValueError("Invalid status") + server.status = args["status"] db.session.commit() return server diff --git a/api/core/plugin/entities/request.py b/api/core/plugin/entities/request.py index 6c0c7f2868..ffd7011a41 100644 --- a/api/core/plugin/entities/request.py +++ b/api/core/plugin/entities/request.py @@ -32,7 +32,7 @@ class RequestInvokeTool(BaseModel): Request to invoke a tool """ - tool_type: Literal["builtin", "workflow", "api"] + tool_type: Literal["builtin", "workflow", "api", "mcp"] provider: str tool: str tool_parameters: dict diff --git a/api/core/tools/mcp_tool/provider.py b/api/core/tools/mcp_tool/provider.py index 1ff9a86327..0bc588e9a8 100644 --- a/api/core/tools/mcp_tool/provider.py +++ b/api/core/tools/mcp_tool/provider.py @@ -53,7 +53,7 @@ class MCPToolProviderController(ToolProviderController): author=db_provider.user.name if db_provider.user else "Anonymous", name=remote_mcp_tool.name, label=I18nObject(en_US=remote_mcp_tool.name, zh_Hans=remote_mcp_tool.name), - provider=db_provider.name, + provider=db_provider.id, icon=db_provider.icon, ), parameters=ToolTransformService.convert_mcp_schema_to_parameter(remote_mcp_tool.inputSchema), diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index 5d20d6cf19..e9a8e0c90c 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -746,7 +746,7 @@ class ToolManager: ) if provider is None: - raise ToolProviderNotFoundError(f"api provider {provider_id} not found") + raise ToolProviderNotFoundError(f"mcp provider {provider_id} not found") controller = MCPToolProviderController._from_db(provider) diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index 0f8408d443..0142595e41 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -1,8 +1,21 @@ +import json + from flask_restful import fields from fields.workflow_fields import workflow_partial_fields from libs.helper import AppIconUrlField, TimestampField + +class JsonStringField(fields.Raw): + def format(self, value): + if isinstance(value, str): + try: + return json.loads(value) + except (json.JSONDecodeError, TypeError): + return value + return value + + app_detail_kernel_fields = { "id": fields.String, "name": fields.String, @@ -220,7 +233,7 @@ app_server_fields = { "server_code": fields.String, "description": fields.String, "status": fields.String, - "parameters": fields.Raw, + "parameters": JsonStringField, "created_at": TimestampField, "updated_at": TimestampField, } diff --git a/api/models/tools.py b/api/models/tools.py index 5c7507759d..9148021404 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -203,6 +203,7 @@ class MCPToolProvider(Base): __table_args__ = ( db.PrimaryKeyConstraint("id", name="tool_mcp_provider_pkey"), db.UniqueConstraint("name", "tenant_id", name="unique_mcp_tool_provider"), + db.UniqueConstraint("server_url", name="unique_mcp_tool_provider_server_url"), ) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) diff --git a/api/services/tools/mcp_tools_mange_service.py b/api/services/tools/mcp_tools_mange_service.py index a8d2a7425f..50142d0297 100644 --- a/api/services/tools/mcp_tools_mange_service.py +++ b/api/services/tools/mcp_tools_mange_service.py @@ -1,5 +1,7 @@ import json +from sqlalchemy import or_ + from core.mcp.error import MCPAuthError, MCPConnectionError from core.mcp.mcp_client import MCPClient from core.tools.entities.api_entities import ToolProviderApiEntity @@ -30,12 +32,21 @@ class MCPToolManageService: def create_mcp_provider( tenant_id: str, name: str, server_url: str, user_id: str, icon: str, icon_type: str, icon_background: str ) -> dict: - if ( + existing_provider = ( db.session.query(MCPToolProvider) - .filter(MCPToolProvider.tenant_id == tenant_id, MCPToolProvider.name == name) + .filter( + MCPToolProvider.tenant_id == tenant_id, + or_(MCPToolProvider.name == name, MCPToolProvider.server_url == server_url), + MCPToolProvider.tenant_id == tenant_id, + ) .first() - ): - raise ValueError(f"MCP tool {name} already exists") + ) + if existing_provider: + if existing_provider.name == name: + raise ValueError(f"MCP tool {name} already exists") + else: + raise ValueError(f"MCP tool {server_url} already exists") + mcp_tool = MCPToolProvider( tenant_id=tenant_id, name=name,