From 8b1c08e5c5abbff06feadee9950b8bd9433e924e Mon Sep 17 00:00:00 2001 From: "sunjiachao.st" Date: Mon, 27 Apr 2026 17:19:29 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(api=20parse)=E4=BB=8Etos=E4=B8=AD?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=86=85=E9=83=A8=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp_server_supabase/README.md | 1 - server/mcp_server_supabase/README_zh.md | 1 - .../platform/aidap_client.py | 89 ++++++++++++++++--- 3 files changed, 79 insertions(+), 12 deletions(-) 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 7c3e9501..60ea55d5 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 = os.getenv("SUPABASE_ENDPOINT_SCHEME", "http").strip().lower() or "http" try: import volcenginesdkcore @@ -69,6 +68,75 @@ 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 _normalize_port(self, port: Any) -> Optional[int]: + if port is None: + return None + try: + return int(port) + except (TypeError, ValueError): + logger.warning("Invalid endpoint port from AIDAP: %s", port) + return None + + def _normalize_scheme(self, scheme: Any) -> Optional[str]: + if not isinstance(scheme, str): + return None + normalized = scheme.strip().lower() + if normalized in {"http", "https"}: + return normalized + return None + + def _scheme_from_port(self, port: Optional[int]) -> str: + return "https" if port == 443 else "http" + + 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]) -> str: + domain = address["domain"] + parsed_scheme = None + host = domain.strip().rstrip("/") + if "://" in host: + parsed = urlsplit(host) + parsed_scheme = self._normalize_scheme(parsed.scheme) + host = parsed.netloc or parsed.path + + port = self._normalize_port(address.get("port")) + scheme = address.get("scheme") or parsed_scheme or self._scheme_from_port(port) + if port is not None and 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"), + "scheme": self._normalize_scheme( + self._pick_value( + addr, + "address_scheme", + "AddressScheme", + "scheme", + "Scheme", + "protocol", + "Protocol", + ) + ), + "address_type": self._pick_value(addr, "address_type", "AddressType"), + } + + def _is_public_address(self, address: dict[str, Any]) -> bool: + address_type = address.get("address_type") + if isinstance(address_type, str) and address_type.lower() == "public": + return True + domain = address["domain"] + return "volces.com" in domain and "ivolces.com" not in domain + def _branch_payload(self, branch: Any, fallback_name: Optional[str] = None) -> dict: parent_branch = self._pick_value(branch, "parent_branch") parent_id = self._pick_value(parent_branch, "branch_id", "parent_id") @@ -289,19 +357,20 @@ async def get_endpoint(self, workspace_id: str, branch_id: Optional[str] = None) response = self.client.describe_workspace_endpoint(request) if hasattr(response, 'endpoints') and response.endpoints: - domains = [] + addresses = [] for endpoint in response.endpoints: if hasattr(endpoint, 'addresses') and endpoint.addresses: for addr in endpoint.addresses: - if hasattr(addr, 'address_domain'): - domains.append(addr.address_domain) + address = self._endpoint_address_payload(addr) + if address: + addresses.append(address) - for domain in domains: - if 'volces.com' in domain and 'ivolces.com' not in domain: - return f"https://{domain}" if ENDPOINT_SCHEME == "https" else f"http://{domain}:80" + for address in addresses: + if self._is_public_address(address): + return self._endpoint_url(address) - if domains: - return f"https://{domains[0]}" if ENDPOINT_SCHEME == "https" else f"http://{domains[0]}:80" + if addresses: + return self._endpoint_url(addresses[0]) return None except Exception as e: From 963a235f7f729cddcd52efaf9de3885f179bf61b Mon Sep 17 00:00:00 2001 From: "sunjiachao.st" Date: Mon, 27 Apr 2026 17:25:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(api=20parse)=E4=BB=8Etos=E4=B8=AD?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=86=85=E9=83=A8=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/aidap_client.py | 96 +++---------------- 1 file changed, 15 insertions(+), 81 deletions(-) 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 6c7225aa..aa58fed5 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 @@ -9,10 +9,6 @@ from ..utils import pick_value logger = logging.getLogger(__name__) -<<<<<<< main -======= -ENDPOINT_SCHEME_FALLBACK = os.getenv("SUPABASE_ENDPOINT_SCHEME", "http").strip().lower() or "http" ->>>>>>> main try: import volcenginesdkcore @@ -72,7 +68,6 @@ 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) -<<<<<<< main def _normalize_port(self, port: Any) -> Optional[int]: if port is None: return None @@ -82,14 +77,6 @@ def _normalize_port(self, port: Any) -> Optional[int]: logger.warning("Invalid endpoint port from AIDAP: %s", port) return None - def _normalize_scheme(self, scheme: Any) -> Optional[str]: - if not isinstance(scheme, str): - return None - normalized = scheme.strip().lower() - if normalized in {"http", "https"}: - return normalized - return None - def _scheme_from_port(self, port: Optional[int]) -> str: return "https" if port == 443 else "http" @@ -101,15 +88,13 @@ def _host_has_port(self, host: str) -> bool: def _endpoint_url(self, address: dict[str, Any]) -> str: domain = address["domain"] - parsed_scheme = None host = domain.strip().rstrip("/") if "://" in host: parsed = urlsplit(host) - parsed_scheme = self._normalize_scheme(parsed.scheme) host = parsed.netloc or parsed.path port = self._normalize_port(address.get("port")) - scheme = address.get("scheme") or parsed_scheme or self._scheme_from_port(port) + scheme = self._scheme_from_port(port) if port is not None and not self._host_has_port(host): host = f"{host}:{port}" return f"{scheme}://{host}" @@ -121,64 +106,27 @@ def _endpoint_address_payload(self, addr: Any) -> Optional[dict[str, Any]]: return { "domain": domain, "port": self._pick_value(addr, "address_port", "AddressPort", "port", "Port"), - "scheme": self._normalize_scheme( - self._pick_value( - addr, - "address_scheme", - "AddressScheme", - "scheme", - "Scheme", - "protocol", - "Protocol", - ) - ), "address_type": self._pick_value(addr, "address_type", "AddressType"), } - def _is_public_address(self, address: dict[str, Any]) -> bool: + 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 True - domain = address["domain"] - return "volces.com" in domain and "ivolces.com" not in domain -======= - def _build_endpoint_from_address(self, address: Any) -> Optional[str]: - domain = self._pick_value(address, "address_domain", "AddressDomain") - if not domain: - return None - - raw_port = self._pick_value(address, "address_port", "AddressPort") - try: - port = int(raw_port) if raw_port is not None else None - except (TypeError, ValueError): - port = None - - if port == 80: - scheme = "http" - elif port == 443: - scheme = "https" - else: - scheme = ENDPOINT_SCHEME_FALLBACK + return 0 + if isinstance(address_type, str) and address_type.lower() == "private": + return 1 + return 2 - if port is None: - return f"{scheme}://{domain}" - return f"{scheme}://{domain}:{port}" - - def _endpoint_priority(self, endpoint: Any, address: Any) -> tuple[int, int, int]: + 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() - address_type = str(self._pick_value(address, "address_type", "AddressType") or "").lower() - domain = str(self._pick_value(address, "address_domain", "AddressDomain") or "").lower() - - is_public = address_type == "public" or ("volces.com" in domain and "ivolces.com" not in domain) 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, ) ->>>>>>> main def _branch_payload(self, branch: Any, fallback_name: Optional[str] = None) -> dict: parent_branch = self._pick_value(branch, "parent_branch") @@ -400,35 +348,21 @@ async def get_endpoint(self, workspace_id: str, branch_id: Optional[str] = None) response = self.client.describe_workspace_endpoint(request) if hasattr(response, 'endpoints') and response.endpoints: -<<<<<<< main - addresses = [] - for endpoint in response.endpoints: - if hasattr(endpoint, 'addresses') and endpoint.addresses: - for addr in endpoint.addresses: - address = self._endpoint_address_payload(addr) - if address: - addresses.append(address) - - for address in addresses: - if self._is_public_address(address): - return self._endpoint_url(address) - - if addresses: - return self._endpoint_url(addresses[0]) -======= candidates: list[tuple[tuple[int, int, int], str]] = [] 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 - candidates.append((self._endpoint_priority(endpoint, addr), resolved_endpoint)) + candidates.append(( + self._endpoint_priority(endpoint, address), + self._endpoint_url(address), + )) if candidates: candidates.sort(key=lambda item: item[0]) return candidates[0][1] ->>>>>>> main return None except Exception as e: From 7602da29efff4c6b4122556152979864ee379ab8 Mon Sep 17 00:00:00 2001 From: "sunjiachao.st" Date: Mon, 27 Apr 2026 17:55:38 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(api=20parse)=E4=BB=8Etos=E4=B8=AD?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=86=85=E9=83=A8=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/aidap_client.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) 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 aa58fed5..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 @@ -77,7 +77,7 @@ def _normalize_port(self, port: Any) -> Optional[int]: logger.warning("Invalid endpoint port from AIDAP: %s", port) return None - def _scheme_from_port(self, port: Optional[int]) -> str: + def _scheme_from_port(self, port: int) -> str: return "https" if port == 443 else "http" def _host_has_port(self, host: str) -> bool: @@ -86,16 +86,22 @@ def _host_has_port(self, host: str) -> bool: except ValueError: return ":" in host.rsplit("]", 1)[-1] - def _endpoint_url(self, address: dict[str, Any]) -> str: - domain = address["domain"] + 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 None + host = domain.strip().rstrip("/") if "://" in host: parsed = urlsplit(host) host = parsed.netloc or parsed.path - port = self._normalize_port(address.get("port")) scheme = self._scheme_from_port(port) - if port is not None and not self._host_has_port(host): + if not self._host_has_port(host): host = f"{host}:{port}" return f"{scheme}://{host}" @@ -355,9 +361,12 @@ async def get_endpoint(self, workspace_id: str, branch_id: Optional[str] = None) 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, address), - self._endpoint_url(address), + endpoint_url, )) if candidates: