Skip to content

Commit 69d102e

Browse files
authored
supabase mcp server 增加 Agent Plan 支持 (#406)
1 parent b5191a6 commit 69d102e

9 files changed

Lines changed: 1123 additions & 512 deletions

File tree

server/mcp_server_supabase/src/mcp_server_supabase/access_policy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"functions",
1414
"storage",
1515
"branching",
16+
"compute",
1617
)
1718
DEFAULT_FEATURE_GROUPS = frozenset({
1819
"account",
@@ -22,6 +23,7 @@
2223
"docs",
2324
"functions",
2425
"branching",
26+
"compute",
2527
})
2628

2729
ALL_TOOL_NAMES = frozenset(tool.name for tool in TOOL_DEFINITIONS)

server/mcp_server_supabase/src/mcp_server_supabase/platform/aidap_client.py

Lines changed: 220 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import os
23
import asyncio
34
import random
45
from collections.abc import Callable
@@ -28,6 +29,14 @@
2829
ComputeSettingsForCreateWorkspaceInput,
2930
StartWorkspaceRequest,
3031
StopWorkspaceRequest,
32+
DescribeWorkspaceDetailRequest,
33+
DescribeComputesRequest,
34+
ModifyComputeSettingsRequest,
35+
CreateDatabaseRequest,
36+
DescribeDatabasesRequest,
37+
CreateDBAccountRequest,
38+
DescribeDBAccountsRequest,
39+
DescribeDBAccountConnectionRequest,
3140
)
3241
except ImportError:
3342
logger.error("volcenginesdkaidap client dependencies not installed")
@@ -254,6 +263,10 @@ async def create_workspace(
254263
workspace_name: str,
255264
engine_type: str = "Supabase",
256265
engine_version: str = "Supabase_1_24",
266+
agent_plan_api_key: Optional[str] = None,
267+
min_cu: float = 0.25,
268+
max_cu: float = 1,
269+
suspend_timeout_seconds: int = 300,
257270
) -> dict:
258271
try:
259272
request = CreateWorkspaceRequest(
@@ -262,15 +275,18 @@ async def create_workspace(
262275
engine_version=engine_version,
263276
branch_settings=BranchSettingsForCreateWorkspaceInput(branch_name="main"),
264277
compute_settings=ComputeSettingsForCreateWorkspaceInput(
265-
auto_scaling_limit_min_cu=0.25,
266-
auto_scaling_limit_max_cu=1,
267-
suspend_timeout_seconds=300
278+
auto_scaling_limit_min_cu=min_cu,
279+
auto_scaling_limit_max_cu=max_cu,
280+
suspend_timeout_seconds=suspend_timeout_seconds
268281
),
269282
workspace_settings=WorkspaceSettingsForCreateWorkspaceInput(
270283
public_connection="Disabled",
271284
deletion_protection="Disabled"
272285
),
273286
)
287+
agent_plan_api_key = agent_plan_api_key or os.getenv("ARK_AGENT_PLAN_API_KEY")
288+
if agent_plan_api_key:
289+
request.agent_plan_api_key = agent_plan_api_key
274290
response = self.client.create_workspace(request)
275291

276292
workspace_id = getattr(response, 'workspace_id', None)
@@ -291,6 +307,207 @@ async def create_workspace(
291307
"error": str(e),
292308
}
293309

310+
async def get_workspace_detail(self, workspace_id: str) -> Optional[Any]:
311+
try:
312+
request = DescribeWorkspaceDetailRequest(workspace_id=workspace_id)
313+
response = self.client.describe_workspace_detail(request)
314+
return getattr(response, "workspace", None)
315+
except Exception as e:
316+
logger.error(f"Error getting workspace detail: {e}")
317+
raise RuntimeError(str(e))
318+
319+
async def describe_computes(
320+
self,
321+
workspace_id: str,
322+
branch_id: Optional[str] = None,
323+
service_type: Optional[str] = None,
324+
) -> list[dict]:
325+
if not branch_id:
326+
branch_id = await self.get_default_branch_id(workspace_id)
327+
if not branch_id:
328+
raise RuntimeError(f"Could not resolve default branch for workspace {workspace_id}")
329+
request_kwargs = {"workspace_id": workspace_id, "branch_id": branch_id}
330+
if service_type:
331+
request_kwargs["service_type"] = service_type
332+
request = DescribeComputesRequest(**request_kwargs)
333+
response = self.client.describe_computes(request)
334+
computes = []
335+
for compute in getattr(response, "computes", []) or []:
336+
computes.append(compute.to_dict() if hasattr(compute, "to_dict") else compute)
337+
return computes
338+
339+
async def modify_compute_settings(
340+
self,
341+
workspace_id: str,
342+
min_cu: float,
343+
max_cu: float,
344+
suspend_timeout_seconds: Optional[int] = None,
345+
service_type: Optional[str] = None,
346+
) -> dict:
347+
try:
348+
request_kwargs = {
349+
"workspace_id": workspace_id,
350+
"auto_scaling_limit_min_cu": min_cu,
351+
"auto_scaling_limit_max_cu": max_cu,
352+
}
353+
if suspend_timeout_seconds is not None:
354+
request_kwargs["suspend_timeout_seconds"] = suspend_timeout_seconds
355+
if service_type:
356+
request_kwargs["service_type"] = service_type
357+
request = ModifyComputeSettingsRequest(**request_kwargs)
358+
self.client.modify_compute_settings(request)
359+
return {"success": True, "workspace_id": workspace_id}
360+
except Exception as e:
361+
logger.error(f"Error modifying compute settings: {e}")
362+
return {"success": False, "error": str(e)}
363+
364+
async def _ensure_branch_id(self, workspace_id: str, branch_id: Optional[str] = None) -> str:
365+
if branch_id:
366+
return branch_id
367+
resolved = await self.get_default_branch_id(workspace_id)
368+
if not resolved:
369+
raise RuntimeError(f"Could not resolve default branch for workspace {workspace_id}")
370+
return resolved
371+
372+
async def _resolve_primary_compute_id(self, workspace_id: str, branch_id: str) -> Optional[str]:
373+
computes = await self.describe_computes(workspace_id, branch_id=branch_id)
374+
for compute in computes:
375+
if str(compute.get("compute_role") or "").lower() == "primary":
376+
return compute.get("compute_id")
377+
return computes[0].get("compute_id") if computes else None
378+
379+
async def describe_databases(
380+
self,
381+
workspace_id: str,
382+
branch_id: Optional[str] = None,
383+
search: Optional[str] = None,
384+
limit: Optional[int] = None,
385+
offset: Optional[int] = None,
386+
) -> list[dict]:
387+
branch_id = await self._ensure_branch_id(workspace_id, branch_id)
388+
request_kwargs = {"workspace_id": workspace_id, "branch_id": branch_id}
389+
if search:
390+
request_kwargs["search"] = search
391+
if limit is not None:
392+
request_kwargs["limit"] = limit
393+
if offset is not None:
394+
request_kwargs["offset"] = offset
395+
request = DescribeDatabasesRequest(**request_kwargs)
396+
response = self.client.describe_databases(request)
397+
databases = []
398+
for database in getattr(response, "databases", []) or []:
399+
databases.append(database.to_dict() if hasattr(database, "to_dict") else database)
400+
return databases
401+
402+
async def create_database(
403+
self,
404+
workspace_id: str,
405+
database_name: str,
406+
branch_id: Optional[str] = None,
407+
database_owner: Optional[str] = None,
408+
database_desc: Optional[str] = None,
409+
) -> dict:
410+
try:
411+
branch_id = await self._ensure_branch_id(workspace_id, branch_id)
412+
request_kwargs = {
413+
"workspace_id": workspace_id,
414+
"branch_id": branch_id,
415+
"database_name": database_name,
416+
}
417+
if database_owner:
418+
request_kwargs["database_owner"] = database_owner
419+
if database_desc:
420+
request_kwargs["database_desc"] = database_desc
421+
request = CreateDatabaseRequest(**request_kwargs)
422+
response = self.client.create_database(request)
423+
database = getattr(response, "database", None)
424+
return {
425+
"success": True,
426+
"database": database.to_dict() if hasattr(database, "to_dict") else database,
427+
}
428+
except Exception as e:
429+
logger.error(f"Error creating database: {e}")
430+
return {"success": False, "error": str(e)}
431+
432+
async def describe_db_accounts(
433+
self,
434+
workspace_id: str,
435+
branch_id: Optional[str] = None,
436+
search: Optional[str] = None,
437+
limit: Optional[int] = None,
438+
offset: Optional[int] = None,
439+
) -> list[dict]:
440+
branch_id = await self._ensure_branch_id(workspace_id, branch_id)
441+
request_kwargs = {"workspace_id": workspace_id, "branch_id": branch_id}
442+
if search:
443+
request_kwargs["search"] = search
444+
if limit is not None:
445+
request_kwargs["limit"] = limit
446+
if offset is not None:
447+
request_kwargs["offset"] = offset
448+
request = DescribeDBAccountsRequest(**request_kwargs)
449+
response = self.client.describe_db_accounts(request)
450+
accounts = []
451+
for account in getattr(response, "accounts", []) or []:
452+
accounts.append(account.to_dict() if hasattr(account, "to_dict") else account)
453+
return accounts
454+
455+
async def create_db_account(
456+
self,
457+
workspace_id: str,
458+
account_name: str,
459+
account_password: str,
460+
branch_id: Optional[str] = None,
461+
account_desc: Optional[str] = None,
462+
) -> dict:
463+
try:
464+
branch_id = await self._ensure_branch_id(workspace_id, branch_id)
465+
request_kwargs = {
466+
"workspace_id": workspace_id,
467+
"branch_id": branch_id,
468+
"account_name": account_name,
469+
"account_password": account_password,
470+
}
471+
if account_desc:
472+
request_kwargs["account_desc"] = account_desc
473+
request = CreateDBAccountRequest(**request_kwargs)
474+
response = self.client.create_db_account(request)
475+
account = getattr(response, "account", None)
476+
return {
477+
"success": True,
478+
"account": account.to_dict() if hasattr(account, "to_dict") else account,
479+
}
480+
except Exception as e:
481+
logger.error(f"Error creating db account: {e}")
482+
return {"success": False, "error": str(e)}
483+
484+
async def describe_db_account_connection(
485+
self,
486+
workspace_id: str,
487+
account_name: str,
488+
database_name: str,
489+
branch_id: Optional[str] = None,
490+
compute_id: Optional[str] = None,
491+
address_id: Optional[str] = None,
492+
) -> dict:
493+
branch_id = await self._ensure_branch_id(workspace_id, branch_id)
494+
if not compute_id:
495+
compute_id = await self._resolve_primary_compute_id(workspace_id, branch_id)
496+
if not compute_id:
497+
raise RuntimeError(f"Could not resolve compute for workspace {workspace_id}")
498+
request_kwargs = {
499+
"workspace_id": workspace_id,
500+
"branch_id": branch_id,
501+
"account_name": account_name,
502+
"database_name": database_name,
503+
"compute_id": compute_id,
504+
}
505+
if address_id:
506+
request_kwargs["address_id"] = address_id
507+
request = DescribeDBAccountConnectionRequest(**request_kwargs)
508+
response = self.client.describe_db_account_connection(request)
509+
return response.to_dict() if hasattr(response, "to_dict") else response
510+
294511
async def start_workspace(self, workspace_id: str) -> dict:
295512
try:
296513
request = StartWorkspaceRequest(workspace_id=workspace_id)

server/mcp_server_supabase/src/mcp_server_supabase/runtime.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
from typing import Any, Callable
33

44
from .platform import AidapClient
5-
from .tools import DatabaseTools, EdgeFunctionTools, StorageTools, WorkspaceTools
5+
from .tools import (
6+
ComputeTools,
7+
DatabaseAdminTools,
8+
DatabaseTools,
9+
EdgeFunctionTools,
10+
StorageTools,
11+
WorkspaceTools,
12+
)
613

714

815
@dataclass(slots=True)
@@ -11,7 +18,9 @@ class SupabaseRuntime:
1118
edge_tools: EdgeFunctionTools
1219
storage_tools: StorageTools
1320
database_tools: DatabaseTools
21+
database_admin_tools: DatabaseAdminTools
1422
workspace_tools: WorkspaceTools
23+
compute_tools: ComputeTools
1524

1625

1726
def create_runtime(
@@ -24,5 +33,7 @@ def create_runtime(
2433
edge_tools=EdgeFunctionTools(client),
2534
storage_tools=StorageTools(client),
2635
database_tools=DatabaseTools(client),
36+
database_admin_tools=DatabaseAdminTools(client),
2737
workspace_tools=WorkspaceTools(client),
38+
compute_tools=ComputeTools(client),
2839
)

0 commit comments

Comments
 (0)