Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion server/mcp_server_supabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 0 additions & 1 deletion server/mcp_server_supabase/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | 兼容保留的端口变量 |
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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])
Expand Down
Loading