Skip to content

feat: add supabase local mcp#380

Merged
cuericlee merged 21 commits intovolcengine:mainfrom
sjcsjcsjc:main
Mar 6, 2026
Merged

feat: add supabase local mcp#380
cuericlee merged 21 commits intovolcengine:mainfrom
sjcsjcsjc:main

Conversation

@sjcsjcsjc
Copy link
Contributor

No description provided.

@YIDWang
Copy link

YIDWang commented Mar 6, 2026

LGTM

Copy link
Collaborator

@cuericlee cuericlee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@cuericlee cuericlee merged commit 19f87f5 into volcengine:main Mar 6, 2026
2 checks passed
@cuericlee cuericlee requested a review from Copilot March 6, 2026 07:59
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Supabase MCP server implementation backed by AIDAP, exposing workspace/branch, database, storage, and edge-function operations via FastMCP.

Changes:

  • Introduces AIDAP and Supabase HTTP clients plus runtime/tool registration wiring for MCP.
  • Adds tool implementations for workspaces/branches, SQL, storage buckets/config, and edge function CRUD/deploy.
  • Adds shared utilities (JSON formatting, error decorators, target resolution), models, and documentation.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
server/mcp_server_supabase/src/mcp_server_supabase/utils/targets.py Adds helpers to pick/resolve workspace vs branch targets.
server/mcp_server_supabase/src/mcp_server_supabase/utils/decorators.py Adds async decorators for JSON serialization and error/read-only handling.
server/mcp_server_supabase/src/mcp_server_supabase/utils/common.py Adds JSON helpers and generic field picking/compaction utilities.
server/mcp_server_supabase/src/mcp_server_supabase/utils/init.py Exposes utils as a package API surface.
server/mcp_server_supabase/src/mcp_server_supabase/tools/workspace_tools.py Implements workspace/branch listing, creation, deletion, endpoints, keys.
server/mcp_server_supabase/src/mcp_server_supabase/tools/storage_tools.py Implements storage bucket/config operations via Supabase REST API.
server/mcp_server_supabase/src/mcp_server_supabase/tools/edge_function_tools.py Implements edge function list/get/deploy/delete via Supabase REST API.
server/mcp_server_supabase/src/mcp_server_supabase/tools/database_tools.py Implements SQL execution and schema/type generation utilities.
server/mcp_server_supabase/src/mcp_server_supabase/tools/base.py Adds shared base class for resolving targets and building Supabase clients.
server/mcp_server_supabase/src/mcp_server_supabase/tools/init.py Exposes tool classes as a package API surface.
server/mcp_server_supabase/src/mcp_server_supabase/tool_registry.py Registers tool functions with FastMCP.
server/mcp_server_supabase/src/mcp_server_supabase/server.py Adds server entrypoint and FastMCP initialization.
server/mcp_server_supabase/src/mcp_server_supabase/runtime.py Adds runtime container and factory wiring clients/tools together.
server/mcp_server_supabase/src/mcp_server_supabase/platform/supabase_client.py Adds async HTTP client wrapper with retries and structured errors.
server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py Adds AIDAP SDK wrapper for workspace/branch, endpoints, and key retrieval.
server/mcp_server_supabase/src/mcp_server_supabase/platform/init.py Exposes platform clients as a package API surface.
server/mcp_server_supabase/src/mcp_server_supabase/models/workspace.py Adds Pydantic models for workspace/branch/key structures.
server/mcp_server_supabase/src/mcp_server_supabase/models/storage.py Adds Pydantic models for storage bucket/config structures.
server/mcp_server_supabase/src/mcp_server_supabase/models/edge_function.py Adds Pydantic models for edge function structures.
server/mcp_server_supabase/src/mcp_server_supabase/models/database.py Adds Pydantic models for database schema/migration structures.
server/mcp_server_supabase/src/mcp_server_supabase/models/init.py Exposes models as a package API surface.
server/mcp_server_supabase/src/mcp_server_supabase/config.py Adds env/config parsing and in-memory caches for endpoints/keys/branches.
server/mcp_server_supabase/src/mcp_server_supabase/init.py Adds package metadata.
server/mcp_server_supabase/pyproject.toml Defines packaging metadata, dependencies, and tooling config.
server/mcp_server_supabase/README_zh.md Adds Chinese documentation for tools, config, and deployment.
server/mcp_server_supabase/README.md Adds English documentation for tools, config, and deployment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +72 to +81
if content:
response = await client.request(
method, url, content=content, headers=default_headers,
params=params, timeout=timeout
)
else:
response = await client.request(
method, url, json=json_data, headers=default_headers,
params=params, timeout=timeout
)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content is checked using truthiness, so content=b\"\" (or other falsy payloads) will incorrectly fall back to JSON mode. Use an explicit content is not None check so callers can intentionally send empty bodies/binary payloads.

Copilot uses AI. Check for mistakes.
raise ValueError("Bucket name cannot be empty")
ws_id, branch_id = await self._resolve_target(workspace_id)
client = await self._get_client(ws_id, branch_id)
response = await client.call_api(f"/storage/v1/bucket/{bucket_name}", method="DELETE")
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bucket_name is interpolated directly into the URL path without encoding, which can break requests for names containing reserved characters (e.g., spaces, /, ?) and may enable path confusion. URL-encode the path segment (e.g., with urllib.parse.quote) and use the stripped/normalized name consistently.

Copilot uses AI. Check for mistakes.
"name": bucket_name,
"public": public
}
if file_size_limit:
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition drops valid values like 0 because it relies on truthiness. If 0 is intended to be a meaningful limit value, check file_size_limit is not None instead.

Suggested change
if file_size_limit:
if file_size_limit is not None:

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +33
def handle_errors(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args, **kwargs) -> str:
try:
result = await func(*args, **kwargs)
if isinstance(result, str):
return result
if isinstance(result, list):
if result and hasattr(result[0], 'model_dump'):
result = [item.model_dump() for item in result]
elif hasattr(result, 'model_dump'):
result = result.model_dump()
return to_json(result)
except Exception as e:
error_msg = format_error(e)
logger.error(f"Error in {func.__name__}: {error_msg}")
return to_json({"error": error_msg})
return wrapper
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_errors coerces successful results into a JSON str (and also returns a JSON str on errors). Several decorated tool methods are annotated as returning dict/List[...]/Pydantic models, which no longer matches runtime behavior and can mislead FastMCP schema generation and static tooling. Either (a) update decorated method return annotations to str, or (b) change the decorator to return the original type and perform JSON serialization at the MCP boundary.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +45
logger.info(f"[DEBUG] Got endpoint for {workspace_id} branch={branch_id}: {endpoint}")
if not endpoint:
target = branch_id or workspace_id
raise ValueError(f"Could not get endpoint for target {target}")

api_key = await self.aidap.get_api_key(workspace_id, "service_role", branch_id=branch_id)
logger.info(f"[DEBUG] Got API key for {workspace_id} branch={branch_id}: {'yes' if api_key else 'no'}")
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are labeled as debug logs but logged at INFO, which will be noisy in production and may reveal internal endpoints. Consider switching to logger.debug(...) (and avoid printing the full endpoint), or gate behind a configurable debug flag.

Suggested change
logger.info(f"[DEBUG] Got endpoint for {workspace_id} branch={branch_id}: {endpoint}")
if not endpoint:
target = branch_id or workspace_id
raise ValueError(f"Could not get endpoint for target {target}")
api_key = await self.aidap.get_api_key(workspace_id, "service_role", branch_id=branch_id)
logger.info(f"[DEBUG] Got API key for {workspace_id} branch={branch_id}: {'yes' if api_key else 'no'}")
logger.debug(f"[DEBUG] Got endpoint for {workspace_id} branch={branch_id}")
if not endpoint:
target = branch_id or workspace_id
raise ValueError(f"Could not get endpoint for target {target}")
api_key = await self.aidap.get_api_key(workspace_id, "service_role", branch_id=branch_id)
logger.debug(f"[DEBUG] Got API key for {workspace_id} branch={branch_id}: {'yes' if api_key else 'no'}")

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling logging.basicConfig(...) and creating mcp = create_mcp() at import time introduces global side effects (affects host logging configuration; eagerly constructs runtime/client dependencies during import). Prefer moving basicConfig and MCP initialization into main() (or under the if __name__ == \"__main__\" guard), or provide a get_app() factory without module-level initialization.

Copilot uses AI. Check for mistakes.
return mcp


mcp = create_mcp()
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling logging.basicConfig(...) and creating mcp = create_mcp() at import time introduces global side effects (affects host logging configuration; eagerly constructs runtime/client dependencies during import). Prefer moving basicConfig and MCP initialization into main() (or under the if __name__ == \"__main__\" guard), or provide a get_app() factory without module-level initialization.

Copilot uses AI. Check for mistakes.

## License

volcengine/mcp-server is licensed under the [MIT License](https://github.com/volcengine/mcp-server/blob/main/LICENSE).
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README states the project is licensed under MIT, but pyproject.toml declares Apache-2.0. Please align the README and packaging metadata to reflect the actual license for this subproject.

Suggested change
volcengine/mcp-server is licensed under the [MIT License](https://github.com/volcengine/mcp-server/blob/main/LICENSE).
volcengine/mcp-server is licensed under the [Apache License 2.0](https://github.com/volcengine/mcp-server/blob/main/LICENSE).

Copilot uses AI. Check for mistakes.
edge_tools = runtime.edge_tools

@mcp.tool()
async def list_edge_functions(workspace_id: str = None) -> str:
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parameters are typed as str but default to None, which is inconsistent with type hints and can degrade generated schemas. Prefer Optional[str] (or str | None) for parameters that accept None.

Copilot uses AI. Check for mistakes.
return await edge_tools.list_edge_functions(workspace_id)

@mcp.tool()
async def get_edge_function(function_name: str, workspace_id: str = None) -> str:
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parameters are typed as str but default to None, which is inconsistent with type hints and can degrade generated schemas. Prefer Optional[str] (or str | None) for parameters that accept None.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants