1
- from typing import Any , Literal
1
+ from typing import TYPE_CHECKING , Any , Literal
2
2
3
3
from supabase_mcp .clients .api_client import ApiClient
4
- from supabase_mcp .core .container import Container
5
4
from supabase_mcp .exceptions import APIError , ConfirmationRequiredError , FeatureAccessError , FeatureTemporaryError
6
5
from supabase_mcp .logger import logger
7
6
from supabase_mcp .services .database .postgres_client import QueryResult
8
7
from supabase_mcp .services .safety .models import ClientType , SafetyMode
9
8
from supabase_mcp .tools .manager import ToolName
10
9
10
+ if TYPE_CHECKING :
11
+ from supabase_mcp .core .container import ServicesContainer
12
+
11
13
12
14
class FeatureManager :
13
15
"""Service for managing features, access to them and their configuration."""
@@ -17,7 +19,6 @@ def __init__(self, api_client: ApiClient):
17
19
18
20
Args:
19
21
api_client: Client for communicating with the API
20
- container: Services container for accessing other services
21
22
"""
22
23
self .api_client = api_client
23
24
@@ -50,99 +51,104 @@ async def check_feature_access(self, feature_name: str) -> None:
50
51
raise FeatureTemporaryError (feature_name ) from e
51
52
raise
52
53
53
- # Tool implementations moved from registry.py
54
-
55
- async def execute_tool (self , tool_name : str , service_container : Container , ** kwargs : Any ) -> Any :
54
+ async def execute_tool (self , tool_name : ToolName , services_container : "ServicesContainer" , ** kwargs : Any ) -> Any :
56
55
"""Execute a tool with feature access check.
57
56
58
57
Args:
59
58
tool_name: Name of the tool to execute
59
+ services_container: Container with all services
60
60
**kwargs: Arguments to pass to the tool
61
61
62
62
Returns:
63
63
Result of the tool execution
64
64
"""
65
65
# Check feature access
66
- await self .check_feature_access (tool_name )
66
+ await self .check_feature_access (tool_name . value )
67
67
68
68
# Execute the appropriate tool based on name
69
69
if tool_name == ToolName .GET_SCHEMAS :
70
- return await self .get_schemas ()
70
+ return await self .get_schemas (services_container )
71
71
elif tool_name == ToolName .GET_TABLES :
72
- return await self .get_tables (** kwargs )
72
+ return await self .get_tables (services_container , ** kwargs )
73
73
elif tool_name == ToolName .GET_TABLE_SCHEMA :
74
- return await self .get_table_schema (** kwargs )
74
+ return await self .get_table_schema (services_container , ** kwargs )
75
75
elif tool_name == ToolName .EXECUTE_POSTGRESQL :
76
- return await self .execute_postgresql (** kwargs )
76
+ return await self .execute_postgresql (services_container , ** kwargs )
77
77
elif tool_name == ToolName .RETRIEVE_MIGRATIONS :
78
- return await self .retrieve_migrations (** kwargs )
78
+ return await self .retrieve_migrations (services_container , ** kwargs )
79
79
elif tool_name == ToolName .SEND_MANAGEMENT_API_REQUEST :
80
- return await self .send_management_api_request (** kwargs )
80
+ return await self .send_management_api_request (services_container , ** kwargs )
81
81
elif tool_name == ToolName .GET_MANAGEMENT_API_SPEC :
82
- return await self .get_management_api_spec (** kwargs )
82
+ return await self .get_management_api_spec (services_container , ** kwargs )
83
83
elif tool_name == ToolName .GET_AUTH_ADMIN_METHODS_SPEC :
84
- return await self .get_auth_admin_methods_spec ()
84
+ return await self .get_auth_admin_methods_spec (services_container )
85
85
elif tool_name == ToolName .CALL_AUTH_ADMIN_METHOD :
86
- return await self .call_auth_admin_method (** kwargs )
86
+ return await self .call_auth_admin_method (services_container , ** kwargs )
87
87
elif tool_name == ToolName .LIVE_DANGEROUSLY :
88
- return await self .live_dangerously (** kwargs )
88
+ return await self .live_dangerously (services_container , ** kwargs )
89
89
elif tool_name == ToolName .CONFIRM_DESTRUCTIVE_OPERATION :
90
- return await self .confirm_destructive_operation (** kwargs )
90
+ return await self .confirm_destructive_operation (services_container , ** kwargs )
91
91
elif tool_name == ToolName .RETRIEVE_LOGS :
92
- return await self .retrieve_logs (** kwargs )
92
+ return await self .retrieve_logs (services_container , ** kwargs )
93
93
else :
94
94
raise ValueError (f"Unknown tool: { tool_name } " )
95
95
96
- async def get_schemas (self ) -> QueryResult :
96
+ async def get_schemas (self , container : "ServicesContainer" ) -> QueryResult :
97
97
"""List all database schemas with their sizes and table counts."""
98
- query_manager = self . container .query_manager
98
+ query_manager = container .query_manager
99
99
query = query_manager .get_schemas_query ()
100
100
return await query_manager .handle_query (query )
101
101
102
- async def get_tables (self , schema_name : str ) -> QueryResult :
102
+ async def get_tables (self , container : "ServicesContainer" , schema_name : str ) -> QueryResult :
103
103
"""List all tables, foreign tables, and views in a schema with their sizes, row counts, and metadata."""
104
- query_manager = self . container .query_manager
104
+ query_manager = container .query_manager
105
105
query = query_manager .get_tables_query (schema_name )
106
106
return await query_manager .handle_query (query )
107
107
108
- async def get_table_schema (self , schema_name : str , table : str ) -> QueryResult :
108
+ async def get_table_schema (self , container : "ServicesContainer" , schema_name : str , table : str ) -> QueryResult :
109
109
"""Get detailed table structure including columns, keys, and relationships."""
110
- query_manager = self . container .query_manager
110
+ query_manager = container .query_manager
111
111
query = query_manager .get_table_schema_query (schema_name , table )
112
112
return await query_manager .handle_query (query )
113
113
114
- async def execute_postgresql (self , query : str , migration_name : str = "" ) -> QueryResult :
114
+ async def execute_postgresql (
115
+ self , container : "ServicesContainer" , query : str , migration_name : str = ""
116
+ ) -> QueryResult :
115
117
"""Execute PostgreSQL statements against your Supabase database."""
116
- query_manager = self . container .query_manager
118
+ query_manager = container .query_manager
117
119
return await query_manager .handle_query (query , has_confirmation = False , migration_name = migration_name )
118
120
119
121
async def retrieve_migrations (
120
122
self ,
123
+ container : "ServicesContainer" ,
121
124
limit : int = 50 ,
122
125
offset : int = 0 ,
123
126
name_pattern : str = "" ,
124
127
include_full_queries : bool = False ,
125
128
) -> QueryResult :
126
129
"""Retrieve a list of all migrations a user has from Supabase."""
127
- query_manager = self . container .query_manager
130
+ query_manager = container .query_manager
128
131
query = query_manager .get_migrations_query (
129
132
limit = limit , offset = offset , name_pattern = name_pattern , include_full_queries = include_full_queries
130
133
)
131
134
return await query_manager .handle_query (query )
132
135
133
136
async def send_management_api_request (
134
137
self ,
138
+ container : "ServicesContainer" ,
135
139
method : str ,
136
140
path : str ,
137
141
path_params : dict [str , str ],
138
142
request_params : dict [str , Any ],
139
143
request_body : dict [str , Any ],
140
144
) -> dict [str , Any ]:
141
145
"""Execute a Supabase Management API request."""
142
- api_manager = self . container .api_manager
146
+ api_manager = container .api_manager
143
147
return await api_manager .execute_request (method , path , path_params , request_params , request_body )
144
148
145
- async def get_management_api_spec (self , params : dict [str , Any ] = {}) -> dict [str , Any ]:
149
+ async def get_management_api_spec (
150
+ self , container : "ServicesContainer" , params : dict [str , Any ] = {}
151
+ ) -> dict [str , Any ]:
146
152
"""Get the Supabase Management API specification."""
147
153
path = params .get ("path" )
148
154
method = params .get ("method" )
@@ -152,21 +158,23 @@ async def get_management_api_spec(self, params: dict[str, Any] = {}) -> dict[str
152
158
logger .debug (
153
159
f"Getting management API spec with path: { path } , method: { method } , domain: { domain } , all_paths: { all_paths } "
154
160
)
155
- api_manager = self . container .api_manager
161
+ api_manager = container .api_manager
156
162
return await api_manager .handle_spec_request (path , method , domain , all_paths )
157
163
158
- async def get_auth_admin_methods_spec (self ) -> dict [str , Any ]:
164
+ async def get_auth_admin_methods_spec (self , container : "ServicesContainer" ) -> dict [str , Any ]:
159
165
"""Get Python SDK methods specification for Auth Admin."""
160
- sdk_client = self . container .sdk_client
166
+ sdk_client = container .sdk_client
161
167
return sdk_client .return_python_sdk_spec ()
162
168
163
- async def call_auth_admin_method (self , method : str , params : dict [str , Any ]) -> dict [str , Any ]:
169
+ async def call_auth_admin_method (
170
+ self , container : "ServicesContainer" , method : str , params : dict [str , Any ]
171
+ ) -> dict [str , Any ]:
164
172
"""Call an Auth Admin method from Supabase Python SDK."""
165
- sdk_client = self . container .sdk_client
173
+ sdk_client = container .sdk_client
166
174
return await sdk_client .call_auth_admin_method (method , params )
167
175
168
176
async def live_dangerously (
169
- self , service : Literal ["api" , "database" ], enable_unsafe_mode : bool = False
177
+ self , container : "ServicesContainer" , service : Literal ["api" , "database" ], enable_unsafe_mode : bool = False
170
178
) -> dict [str , Any ]:
171
179
"""
172
180
Toggle between safe and unsafe operation modes for API or Database services.
@@ -175,7 +183,7 @@ async def live_dangerously(
175
183
- Enable write operations for the database (INSERT, UPDATE, DELETE, schema changes)
176
184
- Enable state-changing operations for the Management API
177
185
"""
178
- safety_manager = self . container .safety_manager
186
+ safety_manager = container .safety_manager
179
187
if service == "api" :
180
188
# Set the safety mode in the safety manager
181
189
new_mode = SafetyMode .UNSAFE if enable_unsafe_mode else SafetyMode .SAFE
@@ -192,11 +200,15 @@ async def live_dangerously(
192
200
return {"service" : "database" , "mode" : safety_manager .get_safety_mode (ClientType .DATABASE )}
193
201
194
202
async def confirm_destructive_operation (
195
- self , operation_type : Literal ["api" , "database" ], confirmation_id : str , user_confirmation : bool = False
203
+ self ,
204
+ container : "ServicesContainer" ,
205
+ operation_type : Literal ["api" , "database" ],
206
+ confirmation_id : str ,
207
+ user_confirmation : bool = False ,
196
208
) -> QueryResult | dict [str , Any ]:
197
209
"""Execute a destructive operation after confirmation. Use this only after reviewing the risks with the user."""
198
- api_manager = self . container .api_manager
199
- query_manager = self . container .query_manager
210
+ api_manager = container .api_manager
211
+ query_manager = container .query_manager
200
212
if not user_confirmation :
201
213
raise ConfirmationRequiredError ("Destructive operation requires explicit user confirmation." )
202
214
@@ -207,6 +219,7 @@ async def confirm_destructive_operation(
207
219
208
220
async def retrieve_logs (
209
221
self ,
222
+ container : "ServicesContainer" ,
210
223
collection : str ,
211
224
limit : int = 20 ,
212
225
hours_ago : int = 1 ,
@@ -219,7 +232,7 @@ async def retrieve_logs(
219
232
f"Tool called: retrieve_logs(collection={ collection } , limit={ limit } , hours_ago={ hours_ago } , filters={ filters } , search={ search } , custom_query={ '<custom>' if custom_query else None } )"
220
233
)
221
234
222
- api_manager = self . container .api_manager
235
+ api_manager = container .api_manager
223
236
result = await api_manager .retrieve_logs (
224
237
collection = collection ,
225
238
limit = limit ,
0 commit comments