diff --git a/server/mcp_server_supabase/README.md b/server/mcp_server_supabase/README.md index 6bd52dfa..843667da 100644 --- a/server/mcp_server_supabase/README.md +++ b/server/mcp_server_supabase/README.md @@ -94,7 +94,6 @@ Static AK/SK can be obtained from the [Volcengine API Access Key console](https: | `DISABLED_TOOLS` | No | - | Comma-separated denylist applied after all other policy filters | | `READ_ONLY` | No | `false` | Startup-level read-only switch; when enabled, mutating tools are hidden | | `SUPABASE_WORKSPACE_SLUG` | No | `default` | Project slug used by Edge Functions APIs | -| `SUPABASE_ENDPOINT_SCHEME` | No | `http` | Endpoint scheme used when building workspace URLs | | `MCP_SERVER_HOST` | No | `0.0.0.0` | Host used by `sse` and `streamable-http` transports | | `MCP_SERVER_PORT` | No | `8000` | Preferred port variable for network transports | | `PORT` | No | `8000` | Backward-compatible port variable | diff --git a/server/mcp_server_supabase/README_zh.md b/server/mcp_server_supabase/README_zh.md index 1359837f..268b7264 100644 --- a/server/mcp_server_supabase/README_zh.md +++ b/server/mcp_server_supabase/README_zh.md @@ -95,7 +95,6 @@ | `DISABLED_TOOLS` | 否 | - | 逗号分隔的工具黑名单,在其他策略之后做最终剔除 | | `READ_ONLY` | 否 | `false` | 服务启动级只读开关;启用后会隐藏所有写工具 | | `SUPABASE_WORKSPACE_SLUG` | 否 | `default` | Edge Functions API 使用的项目 slug | -| `SUPABASE_ENDPOINT_SCHEME` | 否 | `http` | 生成 workspace URL 时使用的协议 | | `MCP_SERVER_HOST` | 否 | `0.0.0.0` | `sse` 和 `streamable-http` 使用的监听地址 | | `MCP_SERVER_PORT` | 否 | `8000` | 网络传输优先使用的端口变量 | | `PORT` | 否 | `8000` | 兼容保留的端口变量 | diff --git a/server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py b/server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py index 284a0c1f..8e307313 100644 --- a/server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py +++ b/server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py @@ -1,15 +1,14 @@ import logging import asyncio -import os import random from collections.abc import Callable from typing import Any, Optional +from urllib.parse import urlsplit from ..config import VOLCENGINE_REGION from ..credentials import resolve_volcengine_credentials from ..utils import pick_value logger = logging.getLogger(__name__) -ENDPOINT_SCHEME_FALLBACK = os.getenv("SUPABASE_ENDPOINT_SCHEME", "http").strip().lower() or "http" try: import volcenginesdkcore @@ -69,39 +68,68 @@ def _branch_error_code(self, error_text: str) -> str: def _pick_value(self, source: Any, *field_names: str) -> Any: return pick_value(source, *field_names) - def _build_endpoint_from_address(self, address: Any) -> Optional[str]: - domain = self._pick_value(address, "address_domain", "AddressDomain") - if not domain: + def _normalize_port(self, port: Any) -> Optional[int]: + if port is None: return None - - raw_port = self._pick_value(address, "address_port", "AddressPort") try: - port = int(raw_port) if raw_port is not None else None + return int(port) except (TypeError, ValueError): - port = None + logger.warning("Invalid endpoint port from AIDAP: %s", port) + return None + + def _scheme_from_port(self, port: int) -> str: + return "https" if port == 443 else "http" - if port == 80: - scheme = "http" - elif port == 443: - scheme = "https" - else: - scheme = ENDPOINT_SCHEME_FALLBACK + def _host_has_port(self, host: str) -> bool: + try: + return urlsplit(f"//{host}").port is not None + except ValueError: + return ":" in host.rsplit("]", 1)[-1] + def _endpoint_url(self, address: dict[str, Any]) -> Optional[str]: + domain = address.get("domain") + if not domain: + return None + + port = self._normalize_port(address.get("port")) if port is None: - return f"{scheme}://{domain}" - return f"{scheme}://{domain}:{port}" + return None - def _endpoint_priority(self, endpoint: Any, address: Any) -> tuple[int, int, int]: - endpoint_type = str(self._pick_value(endpoint, "endpoint_type", "EndpointType") or "").lower() - address_type = str(self._pick_value(address, "address_type", "AddressType") or "").lower() - domain = str(self._pick_value(address, "address_domain", "AddressDomain") or "").lower() + host = domain.strip().rstrip("/") + if "://" in host: + parsed = urlsplit(host) + host = parsed.netloc or parsed.path + + scheme = self._scheme_from_port(port) + if not self._host_has_port(host): + host = f"{host}:{port}" + return f"{scheme}://{host}" + + def _endpoint_address_payload(self, addr: Any) -> Optional[dict[str, Any]]: + domain = self._pick_value(addr, "address_domain", "AddressDomain", "domain", "Domain") + if not domain: + return None + return { + "domain": domain, + "port": self._pick_value(addr, "address_port", "AddressPort", "port", "Port"), + "address_type": self._pick_value(addr, "address_type", "AddressType"), + } + + def _address_type_priority(self, address: dict[str, Any]) -> int: + address_type = address.get("address_type") + if isinstance(address_type, str) and address_type.lower() == "public": + return 0 + if isinstance(address_type, str) and address_type.lower() == "private": + return 1 + return 2 - is_public = address_type == "public" or ("volces.com" in domain and "ivolces.com" not in domain) + def _endpoint_priority(self, endpoint: Any, address: dict[str, Any]) -> tuple[int, int, int]: + endpoint_type = str(self._pick_value(endpoint, "endpoint_type", "EndpointType") or "").lower() is_dashboard = endpoint_type == "dashboard" - has_domain = bool(domain) + has_domain = bool(address.get("domain")) return ( - 0 if is_public else 1, + self._address_type_priority(address), 0 if is_dashboard else 1, 0 if has_domain else 1, ) @@ -330,10 +358,16 @@ async def get_endpoint(self, workspace_id: str, branch_id: Optional[str] = None) for endpoint in response.endpoints: if hasattr(endpoint, 'addresses') and endpoint.addresses: for addr in endpoint.addresses: - resolved_endpoint = self._build_endpoint_from_address(addr) - if not resolved_endpoint: + address = self._endpoint_address_payload(addr) + if not address: + continue + endpoint_url = self._endpoint_url(address) + if not endpoint_url: continue - candidates.append((self._endpoint_priority(endpoint, addr), resolved_endpoint)) + candidates.append(( + self._endpoint_priority(endpoint, address), + endpoint_url, + )) if candidates: candidates.sort(key=lambda item: item[0])