Skip to content

feat: add supabase local mcp#380

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

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

Conversation

@sjcsjcsjc

Copy link
Copy Markdown
Contributor

No description provided.

@YIDWang

YIDWang commented Mar 6, 2026

Copy link
Copy Markdown

LGTM

@cuericlee cuericlee left a comment

Copy link
Copy Markdown
Collaborator

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

Copilot AI left a comment

Copy link
Copy Markdown

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
)

Copilot AI Mar 6, 2026

Copy link

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")

Copilot AI Mar 6, 2026

Copy link

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:

Copilot AI Mar 6, 2026

Copy link

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

Copilot AI Mar 6, 2026

Copy link

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'}")

Copilot AI Mar 6, 2026

Copy link

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"
)

Copilot AI Mar 6, 2026

Copy link

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()

Copilot AI Mar 6, 2026

Copy link

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).

Copilot AI Mar 6, 2026

Copy link

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:

Copilot AI Mar 6, 2026

Copy link

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:

Copilot AI Mar 6, 2026

Copy link

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.
Y-Rookie pushed a commit to Y-Rookie/mcp-server that referenced this pull request Jun 1, 2026
* Add Supabase‑Aidap MCP server with uvx entry

- New supabase‑aidap package (config, models, platform, tools, utils)
- pyproject.toml, uv.lock, README, .gitignore added
- uvx script entry `supabase-aidap` for easy execution
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