Skip to content

Commit 78ced48

Browse files
Merge pull request #17 from alexander-zuev/feat/auth-sdk
Feat/auth sdk
2 parents 1eddd7b + c3791fe commit 78ced48

16 files changed

+1561
-46
lines changed

CHANGELOG.MD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77

8+
## [0.3.6] - 2025-02-26
9+
### Added
10+
- Added `call_auth_admin_method` which enables MCP server to manage users in your database (create, update, delete, confirm). All Auth SDK methods are supported
11+
- Added `get_auth_admin_methods_spec` to retrieve documentation for all available Auth Admin methods. Response objects now use attribute access (dot notation) instead of dictionary access.
12+
13+
### Fixed
14+
- Fixed an issue with improper encoding of database passwords. Previously passwords containing "%" symbol led to connection failures
15+
16+
817
## [0.3.5] - 2025-02-26
918
### Fixed
1019
- Fixed an issue with `get_tables` so that it reliably returns foreign tables and views

README.md

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ A feature-rich MCP server that enables Cursor and Windsurf to safely interact wi
4646
- 🔐 Control read-only and read-write modes of SQL query execution
4747
- 🔄 Robust transaction handling for both direct and pooled database connections
4848
- 💻 Manage your Supabase projects with Supabase Management API
49+
- 🧑‍💻 Manage users with Supabase Auth Admin methods via Python SDK
4950
- 🔨 Pre-built tools to help Cursor & Windsurf work with MCP more effectively
5051
- 📦 Dead-simple install & setup via package manager (uv, pipx, etc.)
5152

@@ -122,7 +123,7 @@ After installing the package, you'll need to configure your database connection
122123
123124
#### Remote Supabase instance
124125

125-
> ⚠️ **IMPORTANT WARNING**: Session pooling connections are NOT supported yet. Use transaction pooling and direct connections instead. I'm planning to add / modify connection to support this in v0.4 (soon).
126+
> ⚠️ **IMPORTANT WARNING**: Session pooling connections are not supported and there are no plans to support it yet. Let me know if you feel there is a use case for supporting this in an MCP server
126127
127128
![Session pool connections are not supported yet](https://github.com/user-attachments/assets/c01e0e06-8fdc-4632-bde5-922c7a527897)
128129

@@ -354,7 +355,6 @@ This MCP server uses::
354355
- **Direct Database Connection**: when connecting to a local Supabase instance
355356
- **Transaction Pooler Connections**: when connecting to a remote Supabase instance
356357

357-
> ⚠️ **IMPORTANT WARNING**: Session pooling connections are NOT SUPPORTED. Use transaction pooling and direct connections instead. I'm planning to add / modify connection to support this in v0.4 (soon).
358358

359359
When connecting via Supabase's Transaction Pooler, some complex transaction patterns may not work as expected. For schema changes in these environments, use explicit transaction blocks or consider using Supabase migrations or the SQL Editor in the dashboard.
360360
@@ -389,6 +389,41 @@ Since v0.3.0 server supports sending arbitrary requests to Supabase Management A
389389
- Allows to switch between safe and unsafe modes dynamically
390390
- Blocked operations (delete project, delete database) are not allowed regardless of the mode
391391
392+
### Auth Admin tools
393+
I was planning to add support for Python SDK methods to the MCP server. Upon consideration I decided to only add support for Auth admin methods as I often found myself manually creating test users which was prone to errors and time consuming. Now I can just ask Cursor to create a test user and it will be done seamlessly. Check out the full Auth Admin SDK method docs to know what it can do.
394+
395+
Since v0.3.6 server supports direct access to Supabase Auth Admin methods via Python SDK:
396+
- Includes the following tools:
397+
- `get_auth_admin_methods_spec` to retrieve documentation for all available Auth Admin methods
398+
- `call_auth_admin_method` to directly invoke Auth Admin methods with proper parameter handling
399+
- Supported methods:
400+
- `get_user_by_id`: Retrieve a user by their ID
401+
- `list_users`: List all users with pagination
402+
- `create_user`: Create a new user
403+
- `delete_user`: Delete a user by their ID
404+
- `invite_user_by_email`: Send an invite link to a user's email
405+
- `generate_link`: Generate an email link for various authentication purposes
406+
- `update_user_by_id`: Update user attributes by ID
407+
- `delete_factor`: Delete a factor on a user (currently not implemented in SDK)
408+
409+
#### Why use Auth Admin SDK instead of raw SQL queries?
410+
411+
The Auth Admin SDK provides several key advantages over direct SQL manipulation:
412+
- **Functionality**: Enables operations not possible with SQL alone (invites, magic links, MFA)
413+
- **Accuracy**: More reliable then creating and executing raw SQL queries on auth schemas
414+
- **Simplicity**: Offers clear methods with proper validation and error handling
415+
416+
- Response format:
417+
- All methods return structured Python objects instead of raw dictionaries
418+
- Object attributes can be accessed using dot notation (e.g., `user.id` instead of `user["id"]`)
419+
- Edge cases and limitations:
420+
- UUID validation: Many methods require valid UUID format for user IDs and will return specific validation errors
421+
- Email configuration: Methods like `invite_user_by_email` and `generate_link` require email sending to be configured in your Supabase project
422+
- Link types: When generating links, different link types have different requirements:
423+
- `signup` links don't require the user to exist
424+
- `magiclink` and `recovery` links require the user to already exist in the system
425+
- Error handling: The server provides detailed error messages from the Supabase API, which may differ from the dashboard interface
426+
- Method availability: Some methods like `delete_factor` are exposed in the API but not fully implemented in the SDK
392427
393428
## Roadmap
394429
@@ -397,14 +432,13 @@ Since v0.3.0 server supports sending arbitrary requests to Supabase Management A
397432
- 🎮 Programmatic access to Supabase management API with safety controls - ✅ (v0.3.0)
398433
- 👷‍♂️ Read and read-write database SQL queries with safety controls - ✅ (v0.3.0)
399434
- 🔄 Robust transaction handling for both direct and pooled connections - ✅ (v0.3.2)
400-
- 🐍 Support methods and objects available in native Python SDK
435+
- 🐍 Support methods and objects available in native Python SDK - ✅ (v0.3.6)
401436
- 🔍 Strong SQL query validation
402437
- 📝 Connect to db logs to help debug errors (Pull in [edge functions logs](https://supabase.com/dashboard/project/drmzszdytvvfbcytltsw/logs/edge-logs))
403438
- 👨‍💻 Supabase CLI integration? (if necessary)
439+
- 🚧 Create a migration file automatically if a migration has been run successfully on the databasae?
404440
405-
### Support of Python SDK methods
406441
407-
I'm planning to add support for Auth methods from Python SDK as it would allow Cursor to create test users for me, which might be handy.
408442
409443
### Connect to Supabase logs
410444

supabase_mcp/api_manager/api_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)
1717

1818
from supabase_mcp.api_manager.api_safety_config import SafetyConfig, SafetyLevel
19-
from supabase_mcp.api_manager.spec_manager import SpecManager
19+
from supabase_mcp.api_manager.api_spec_manager import ApiSpecManager
2020
from supabase_mcp.exceptions import (
2121
APIClientError,
2222
APIConnectionError,
@@ -46,7 +46,7 @@ def __init__(self):
4646
async def create(cls) -> SupabaseApiManager:
4747
"""Factory method to create and initialize an API manager"""
4848
manager = cls()
49-
manager.spec_manager = await SpecManager.create() # Use the running event loop
49+
manager.spec_manager = await ApiSpecManager.create() # Use the running event loop
5050
return manager
5151

5252
@classmethod

supabase_mcp/api_manager/spec_manager.py renamed to supabase_mcp/api_manager/api_spec_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ValidationResult:
2222
operation_info: dict | None = None
2323

2424

25-
class SpecManager:
25+
class ApiSpecManager:
2626
"""
2727
Manages the OpenAPI specification for the Supabase Management API.
2828
Handles spec loading, caching, and validation.
@@ -33,8 +33,8 @@ def __init__(self):
3333
self.spec: dict | None = None
3434

3535
@classmethod
36-
async def create(cls) -> "SpecManager":
37-
"""Async factory method to create and initialize a SpecManager"""
36+
async def create(cls) -> "ApiSpecManager":
37+
"""Async factory method to create and initialize a ApiSpecManager"""
3838
manager = cls()
3939
await manager.on_startup()
4040
return manager

supabase_mcp/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ class APIConnectionError(APIError):
6060
pass
6161

6262

63+
class PythonSDKError(Exception):
64+
"""Failed to create Python SDK client or call Python SDK method"""
65+
66+
pass
67+
68+
6369
class APIResponseError(APIError):
6470
"""Failed to process API response"""
6571

supabase_mcp/main.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from supabase_mcp.db_client.db_safety_config import DbSafetyLevel
1010
from supabase_mcp.logger import logger
1111
from supabase_mcp.queries import PreBuiltQueries
12+
from supabase_mcp.sdk_client.python_client import SupabaseSDKClient
1213
from supabase_mcp.settings import settings
1314
from supabase_mcp.validators import (
1415
validate_schema_name,
@@ -70,6 +71,8 @@ async def get_table_schema(schema_name: str, table: str):
7071
3. NEVER mix READ and WRITE operations in the same query
7172
4. NEVER use single DDL statements without transaction control
7273
5. Remember to enable unsafe mode first with live_dangerously('database', True)
74+
6. For auth operations (primarily creating, updating, deleting users, generating links, etc), prefer using the Auth Admin SDK methods
75+
instead of direct SQL manipulation to ensure correctness and prevent security issues
7376
7477
TRANSACTION HANDLING:
7578
- The server detects BEGIN/COMMIT/ROLLBACK keywords to respect your transaction control
@@ -190,6 +193,73 @@ async def get_management_api_safety_rules() -> dict:
190193
return api_manager.get_safety_rules()
191194

192195

196+
@mcp.tool(
197+
description="""
198+
Get Python SDK methods specification for Auth Admin. Returns a python dictionary of all Auth Python SDK methods.
199+
Use this to understand the available methods and their required parameters.
200+
"""
201+
)
202+
async def get_auth_admin_methods_spec() -> dict:
203+
"""Returns the Python SDK spec"""
204+
sdk_client = await SupabaseSDKClient.get_instance()
205+
return sdk_client.return_python_sdk_spec()
206+
207+
208+
@mcp.tool(
209+
description="""
210+
Call an Auth Admin method from Supabase Python SDK. Returns the result of the method call.
211+
212+
Available methods:
213+
- get_user_by_id: Retrieve a user by their ID
214+
- list_users: List all users with pagination
215+
- create_user: Create a new user
216+
- delete_user: Delete a user by their ID
217+
- invite_user_by_email: Send an invite link to a user's email
218+
- generate_link: Generate an email link for various authentication purposes
219+
- update_user_by_id: Update user attributes by ID
220+
- delete_factor: Delete a factor on a user
221+
222+
Each method requires specific parameters. For nested parameters, follow the structure exactly:
223+
224+
Examples:
225+
1. Get user by ID:
226+
method: "get_user_by_id"
227+
params: {"uid": "user-uuid-here"}
228+
229+
2. Create user:
230+
method: "create_user"
231+
params: {
232+
"attributes": {
233+
"email": "user@example.com",
234+
"password": "secure-password",
235+
"email_confirm": true,
236+
"user_metadata": {"name": "John Doe"}
237+
}
238+
}
239+
240+
3. Generate link:
241+
method: "generate_link"
242+
params: {
243+
"params": {
244+
"type": "signup",
245+
"email": "user@example.com",
246+
"password": "secure-password",
247+
"options": {
248+
"data": {"name": "John Doe"},
249+
"redirect_to": "https://example.com/welcome"
250+
}
251+
}
252+
}
253+
254+
Use get_auth_admin_methods_spec() to see full documentation for all methods.
255+
"""
256+
)
257+
async def call_auth_admin_method(method: str, params: dict) -> dict:
258+
"""Calls a method of the Python SDK client"""
259+
sdk_client = await SupabaseSDKClient.get_instance()
260+
return await sdk_client.call_auth_admin_method(method, params)
261+
262+
193263
def run():
194264
"""Run the Supabase MCP server."""
195265
if settings.supabase_project_ref.startswith("127.0.0.1"):
@@ -205,6 +275,8 @@ def run():
205275
)
206276
if settings.supabase_access_token:
207277
logger.info("Personal access token detected - using for Management API")
278+
if settings.supabase_service_role_key:
279+
logger.info("Service role key detected - using for Python SDK")
208280
mcp.run()
209281

210282

supabase_mcp/sdk_client/__init__.py

Whitespace-only changes.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from typing import Any, Literal
2+
3+
from pydantic import BaseModel, model_validator
4+
5+
6+
class GetUserByIdParams(BaseModel):
7+
uid: str
8+
9+
10+
class ListUsersParams(BaseModel):
11+
page: int | None = 1
12+
per_page: int | None = 50
13+
14+
15+
class CreateUserParams(BaseModel):
16+
email: str | None = None
17+
password: str | None = None
18+
email_confirm: bool | None = False
19+
phone: str | None = None
20+
phone_confirm: bool | None = False
21+
user_metadata: dict[str, Any] | None = None
22+
app_metadata: dict[str, Any] | None = None
23+
role: str | None = None
24+
ban_duration: str | None = None
25+
nonce: str | None = None
26+
27+
@model_validator(mode="after")
28+
def check_email_or_phone(self) -> "CreateUserParams":
29+
if not self.email and not self.phone:
30+
raise ValueError("Either email or phone must be provided")
31+
return self
32+
33+
34+
class DeleteUserParams(BaseModel):
35+
id: str
36+
should_soft_delete: bool | None = False
37+
38+
39+
class InviteUserByEmailParams(BaseModel):
40+
email: str
41+
options: dict[str, Any] | None = None
42+
43+
44+
class GenerateLinkParams(BaseModel):
45+
type: Literal[
46+
"signup", "invite", "magiclink", "recovery", "email_change_current", "email_change_new", "phone_change"
47+
]
48+
email: str
49+
password: str | None = None
50+
new_email: str | None = None
51+
options: dict[str, Any] | None = None
52+
53+
@model_validator(mode="after")
54+
def validate_required_fields(self) -> "GenerateLinkParams":
55+
# Check password for signup
56+
if self.type == "signup" and not self.password:
57+
raise ValueError("Password is required for signup links")
58+
59+
# Check new_email for email change
60+
if self.type in ["email_change_current", "email_change_new"] and not self.new_email:
61+
raise ValueError("new_email is required for email change links")
62+
63+
return self
64+
65+
66+
class UpdateUserByIdParams(BaseModel):
67+
uid: str
68+
email: str | None = None
69+
password: str | None = None
70+
email_confirm: bool | None = False
71+
phone: str | None = None
72+
phone_confirm: bool | None = False
73+
user_metadata: dict[str, Any] | None = None
74+
app_metadata: dict[str, Any] | None = None
75+
role: str | None = None
76+
ban_duration: str | None = None
77+
nonce: str | None = None
78+
79+
80+
class DeleteFactorParams(BaseModel):
81+
id: str
82+
user_id: str
83+
84+
85+
# Map method names to their parameter models
86+
PARAM_MODELS = {
87+
"get_user_by_id": GetUserByIdParams,
88+
"list_users": ListUsersParams,
89+
"create_user": CreateUserParams,
90+
"delete_user": DeleteUserParams,
91+
"invite_user_by_email": InviteUserByEmailParams,
92+
"generate_link": GenerateLinkParams,
93+
"update_user_by_id": UpdateUserByIdParams,
94+
"delete_factor": DeleteFactorParams,
95+
}

0 commit comments

Comments
 (0)