Skip to content

Commit 3d5c450

Browse files
committed
fix: resolve Supabase and PostgreSQL issues
Fixed critical pgvector compatibility issues and improved PostgreSQL/Supabase support: 1. pgvector Schema Auto-Detection (app/backends/postgresql_backend.py): - Automatically detects pgvector extension schema via SQL query - Supports Supabase ('extensions' schema) and local PostgreSQL ('public' schema) - Passes schema to register_vector() for universal compatibility - Strict error handling - fails fast if pgvector unavailable when semantic search enabled 2. Embedding Type Conversion Fixes (app/services/embedding_service.py): - Fixed Ollama numpy.float32 to Python float conversion using .tolist() - asyncpg requires Python float, not numpy.float32 - Added proper type casting for runtime type checking 3. Image Pre-Validation (app/server.py): - Validates all images before database operations - Checks data field presence, base64 encoding, and size limits - Atomic validation - either all images valid or entire operation fails - Prevents partial insertions and data integrity issues 4. Image Error Logging (app/repositories/image_repository.py): - Enhanced error messages with context_id and image index for debugging - Backend-specific logging (SQLite vs PostgreSQL) - Defensive error handling in repository layer 5. Removed Supabase-Specific Code (multiple files): - Deleted IS_SUPABASE, SUPABASE_URL, service_role_key settings - Simplified backend_type from 'sqlite|postgresql|supabase' to 'sqlite|postgresql' - Supabase is now treated as standard PostgreSQL (no special handling) - Removed conditional logic that treated Supabase differently 6. Security Improvements (app/backends/postgresql_backend.py, app/settings.py): - URL-encode passwords in connection strings to handle special characters (#, @, :, /) - Use pydantic SecretStr for password fields - Prevents password exposure in logs and error messages 7. Comprehensive Supabase Documentation: - CLAUDE.md: Added 151 lines covering Direct Connection and Session Pooler methods - README.md: Added 248 lines with setup guides, troubleshooting, decision matrix - docs/semantic-search.md: Added 110 lines for PostgreSQL/Supabase pgvector setup - Includes connection string formats, IPv4/IPv6 considerations, extension enabling 8. Repository Code Cleanup (app/repositories/*.py): - Removed 'supabase' from backend type comments - Simplified conditional logic (postgresql instead of postgresql/supabase) - Removed unnecessary numpy imports from PostgreSQL branches 9. New Tests (tests/test_postgresql_backend.py): - Unit tests for backend type property - Connection string preservation tests - Covers Supabase Direct Connection and Session Pooler formats All changes maintain backward compatibility while fixing critical bugs in pgvector embedding storage for Supabase and improving overall code quality.
1 parent 67db46f commit 3d5c450

17 files changed

Lines changed: 869 additions & 159 deletions

CLAUDE.md

Lines changed: 260 additions & 5 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,254 @@ export ENABLE_SEMANTIC_SEARCH=true # Optional: only if you need semantic search
218218

219219
**Note:** PostgreSQL settings are only needed when using PostgreSQL. The server uses SQLite by default if `STORAGE_BACKEND` is not set.
220220

221+
### Using with Supabase
222+
223+
Supabase is fully compatible with the PostgreSQL backend using direct database connection. No special configuration needed - Supabase IS PostgreSQL.
224+
225+
**Connection Methods:**
226+
227+
Supabase offers TWO connection methods. Choose based on your network capabilities:
228+
229+
1. **Direct Connection** (IPv6 required, lowest latency)
230+
2. **Session Pooler** (IPv4 compatible, universal)
231+
232+
#### Connection Method 1: Direct Connection (Recommended)
233+
234+
Best for: VMs, servers, and local development with IPv6 support
235+
236+
**Requirements:**
237+
- IPv6 connectivity (or paid dedicated IPv4 add-on)
238+
- Port 5432 accessible
239+
- Lowest latency (~15-20ms)
240+
241+
**Quick Setup:**
242+
243+
1. **Get your database connection details:**
244+
245+
Navigate to your Supabase Dashboard:
246+
- Go to **Database → Settings** (left sidebar → Database → Settings)
247+
- Find the **"Connect to your project"** section
248+
- Select **"Connection String"** tab, then **"Direct connection"** method
249+
- You'll see: `postgresql://postgres:[YOUR_PASSWORD]@db.[PROJECT_REF].supabase.co:5432/postgres`
250+
251+
2. **Get or reset your database password:**
252+
253+
**IMPORTANT:** For security reasons, your database password is **never displayed** in the Supabase Dashboard.
254+
255+
You must use one of these approaches:
256+
- **Use the password you set** when creating your Supabase project, OR
257+
- **Click "Reset database password"** (below the connection string) to generate a new password
258+
259+
**Note:** Replace `[YOUR_PASSWORD]` in the connection string with your actual database password (NOT API keys - those are for REST/GraphQL APIs).
260+
261+
3. **Configure the connection:**
262+
263+
Add to your `.mcp.json` with your actual password:
264+
265+
```json
266+
{
267+
"mcpServers": {
268+
"context-server": {
269+
"type": "stdio",
270+
"command": "uvx",
271+
"args": ["mcp-context-server"],
272+
"env": {
273+
"STORAGE_BACKEND": "postgresql",
274+
"POSTGRESQL_CONNECTION_STRING": "postgresql://postgres:your-actual-password@db.[PROJECT_REF].supabase.co:5432/postgres"
275+
}
276+
}
277+
}
278+
}
279+
```
280+
281+
Or using individual environment variables:
282+
283+
```json
284+
{
285+
"mcpServers": {
286+
"context-server": {
287+
"type": "stdio",
288+
"command": "uvx",
289+
"args": ["mcp-context-server"],
290+
"env": {
291+
"STORAGE_BACKEND": "postgresql",
292+
"POSTGRESQL_HOST": "db.[PROJECT_REF].supabase.co",
293+
"POSTGRESQL_PORT": "5432",
294+
"POSTGRESQL_USER": "postgres",
295+
"POSTGRESQL_PASSWORD": "your-actual-password",
296+
"POSTGRESQL_DATABASE": "postgres",
297+
"ENABLE_SEMANTIC_SEARCH": "true"
298+
}
299+
}
300+
}
301+
}
302+
```
303+
304+
**Replace** `[PROJECT_REF]` with your actual project reference ID and `your-actual-password` with your database password.
305+
306+
#### Connection Method 2: Session Pooler (IPv4 Compatible)
307+
308+
Best for: Systems without IPv6 support (Windows, corporate networks, restricted environments)
309+
310+
**Requirements:**
311+
- IPv4 connectivity (works universally)
312+
- Port 5432 accessible
313+
- Slightly higher latency (~20-30ms, +5-10ms vs Direct)
314+
315+
**Important Differences from Direct Connection:**
316+
- **Different hostname**: Uses `*.pooler.supabase.com` (NOT `db.*.supabase.co`)
317+
- **Different username format**: `postgres.[PROJECT-REF]` (includes project reference)
318+
- **Same port**: 5432 (NOT 6543 - that's Transaction Pooler for serverless only)
319+
- **IPv4 compatible**: Works on all systems without IPv6 configuration
320+
321+
**Quick Setup:**
322+
323+
1. **Get your Session Pooler connection string:**
324+
325+
Navigate to your Supabase Dashboard:
326+
- Go to **Database → Settings** (left sidebar → Database → Settings)
327+
- Find the **"Connect to your project"** section
328+
- Select **"Connection String"** tab, then **"Session pooler"** method (NOT "Transaction pooler")
329+
- You'll see: `postgresql://postgres.[PROJECT-REF]:[YOUR_PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres`
330+
331+
**Example:**
332+
```
333+
postgresql://postgres.abcdefghijklmno:your-password@aws-0-us-east-1.pooler.supabase.com:5432/postgres
334+
```
335+
336+
2. **Get or reset your database password** (same as Direct Connection - see above)
337+
338+
3. **Configure the connection:**
339+
340+
Add to your `.mcp.json`:
341+
342+
```json
343+
{
344+
"mcpServers": {
345+
"context-server": {
346+
"type": "stdio",
347+
"command": "uvx",
348+
"args": ["mcp-context-server"],
349+
"env": {
350+
"STORAGE_BACKEND": "postgresql",
351+
"POSTGRESQL_CONNECTION_STRING": "postgresql://postgres.[PROJECT-REF]:your-actual-password@aws-0-[REGION].pooler.supabase.com:5432/postgres"
352+
}
353+
}
354+
}
355+
}
356+
```
357+
358+
Or using individual environment variables:
359+
360+
```json
361+
{
362+
"mcpServers": {
363+
"context-server": {
364+
"type": "stdio",
365+
"command": "uvx",
366+
"args": ["mcp-context-server"],
367+
"env": {
368+
"STORAGE_BACKEND": "postgresql",
369+
"POSTGRESQL_HOST": "aws-0-[REGION].pooler.supabase.com",
370+
"POSTGRESQL_PORT": "5432",
371+
"POSTGRESQL_USER": "postgres.[PROJECT-REF]",
372+
"POSTGRESQL_PASSWORD": "your-actual-password",
373+
"POSTGRESQL_DATABASE": "postgres",
374+
"ENABLE_SEMANTIC_SEARCH": "true"
375+
}
376+
}
377+
}
378+
}
379+
```
380+
381+
**Replace** `[PROJECT-REF]` with your actual project reference, `[REGION]` with your project region (e.g., `us-east-1`), and `your-actual-password` with your database password.
382+
383+
#### Which Connection Method Should I Use?
384+
385+
| Consideration | Direct Connection | Session Pooler |
386+
|--------------|-------------------|----------------|
387+
| **IPv6 Required** | Yes (or paid IPv4 add-on) | No - IPv4 compatible |
388+
| **Latency** | Lowest (~15-20ms) | +5-10ms overhead |
389+
| **Windows Compatibility** | May require IPv6 config | Works universally |
390+
| **Corporate Networks** | May be blocked | Usually works |
391+
| **Configuration** | Simpler (standard PostgreSQL) | Requires correct hostname |
392+
| **Best For** | VMs, servers with IPv6 | Windows, restricted networks |
393+
394+
**Recommendation:**
395+
- **Try Direct Connection first** - it's simpler and faster
396+
- **Switch to Session Pooler if you get "getaddrinfo failed" errors** (indicates IPv6 connectivity issues)
397+
398+
#### Troubleshooting
399+
400+
**"getaddrinfo failed" Error:**
401+
402+
If you see this error with Direct Connection:
403+
```
404+
Error: getaddrinfo ENOTFOUND db.[PROJECT-REF].supabase.co
405+
```
406+
407+
**Solution:** Your system doesn't support IPv6 or it's disabled. Use Session Pooler instead (Method 2 above).
408+
409+
**Why?** Direct Connection (`db.*.supabase.co`) uses IPv6 by default. Session Pooler (`*.pooler.supabase.com`) provides IPv4 compatibility through Supavisor proxy.
410+
411+
**Enabling Semantic Search (pgvector extension):**
412+
413+
If you want to use semantic search with Supabase, you must enable the pgvector extension:
414+
415+
1. **Via Supabase Dashboard** (easiest method):
416+
- Navigate to **Database → Extensions** (left sidebar)
417+
- Search for "vector" in the extensions list
418+
- Find "vector" extension (version 0.8.0+)
419+
- Click the **toggle switch** to enable it (turns green when enabled)
420+
421+
2. **Via SQL Editor** (alternative method):
422+
- Navigate to **SQL Editor** in Supabase Dashboard
423+
- Run: `CREATE EXTENSION IF NOT EXISTS vector;`
424+
- Verify: `SELECT * FROM pg_extension WHERE extname = 'vector';`
425+
426+
3. **Configure environment**:
427+
```json
428+
{
429+
"mcpServers": {
430+
"context-server": {
431+
"type": "stdio",
432+
"command": "uvx",
433+
"args": ["mcp-context-server"],
434+
"env": {
435+
"STORAGE_BACKEND": "postgresql",
436+
"POSTGRESQL_CONNECTION_STRING": "postgresql://postgres:your-actual-password@db.[PROJECT_REF].supabase.co:5432/postgres",
437+
"ENABLE_SEMANTIC_SEARCH": "true"
438+
}
439+
}
440+
}
441+
}
442+
```
443+
444+
**Note:** The pgvector extension is available on all Supabase projects but must be manually enabled. The server will automatically create the necessary vector columns and indexes on first run.
445+
446+
**Why Direct Connection?**
447+
448+
- **Recommended by Supabase** for backend services and server-side applications
449+
- **Full PostgreSQL capabilities**: pgvector (available, must be enabled), JSONB, transactions, all extensions
450+
- **Better performance**: Lower latency than REST API, native connection pooling
451+
- **Production-ready**: MVCC for concurrent writes, connection pooling with asyncpg
452+
- **Zero special code**: Uses standard PostgreSQL backend - no Supabase-specific implementation needed
453+
454+
**Important Notes:**
455+
456+
-**Use database password** from Settings → Database section
457+
-**NOT API keys** - API keys (including legacy service_role key) are for REST/GraphQL APIs, not direct database connection
458+
-**Use port 5432** for direct connection (recommended for backend services)
459+
-**pgvector extension** is available on all Supabase projects - enable it via Dashboard → Extensions for semantic search
460+
-**All PostgreSQL features** work identically - JSONB indexing, metadata filtering, transactions
461+
462+
**Security Best Practices:**
463+
464+
- Store database password in environment variables, not in code
465+
- Use Supabase's connection string format for simplicity
466+
- Enable SSL/TLS by default (handled automatically by Supabase connection)
467+
- Consider using read-only credentials if your use case only needs read access
468+
221469
## API Reference
222470

223471
### Tools

app/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Storage backend implementations for mcp-context-server.
33
44
This package provides a protocol-based abstraction for different database backends,
5-
enabling support for SQLite, PostgreSQL, Supabase, and other storage systems.
5+
enabling support for SQLite, PostgreSQL, and other storage systems.
66
77
Key Components:
88
- StorageBackend: Protocol defining the interface for all storage backends

app/backends/base.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class StorageBackend(Protocol):
2525
"""
2626
Protocol defining the interface for storage backend implementations.
2727
28-
All storage backends (SQLite, PostgreSQL, Supabase, etc.) must implement
28+
All storage backends (SQLite, PostgreSQL, etc.) must implement
2929
this protocol to ensure compatibility with the repository layer.
3030
3131
The protocol defines methods for:
@@ -112,10 +112,6 @@ async def initialize(self) -> None:
112112
- Verifies schema exists
113113
- Configures statement cache
114114
115-
For Supabase:
116-
- Same as PostgreSQL, but connects to Supabase's transaction mode
117-
- Uses service role key for authentication
118-
119115
Raises:
120116
RuntimeError: If initialization fails
121117
ConnectionError: If database is unreachable
@@ -323,7 +319,7 @@ def get_metrics(self) -> dict[str, Any]:
323319
324320
Returns:
325321
Dictionary with metrics. Common keys:
326-
- backend_type: str - Backend identifier (sqlite, postgresql, supabase)
322+
- backend_type: str - Backend identifier (sqlite, postgresql)
327323
- pool_size: int - Total connections in pool
328324
- active_connections: int - Connections currently in use
329325
- circuit_breaker_state: str - Circuit breaker status
@@ -368,7 +364,7 @@ def backend_type(self) -> str:
368364
Get the backend type identifier.
369365
370366
Returns:
371-
Backend type string: 'sqlite', 'postgresql', or 'supabase'
367+
Backend type string: 'sqlite' or 'postgresql'
372368
373369
This property is used for:
374370
- Logging and monitoring

app/backends/factory.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
def create_backend(
19-
backend_type: Literal['sqlite', 'postgresql', 'supabase'] | None = None,
19+
backend_type: Literal['sqlite', 'postgresql'] | None = None,
2020
db_path: Path | str | None = None,
2121
connection_string: str | None = None,
2222
) -> StorageBackend:
@@ -28,14 +28,14 @@ def create_backend(
2828
returning a StorageBackend protocol implementation.
2929
3030
Args:
31-
backend_type: Type of backend to create ('sqlite', 'postgresql', 'supabase').
31+
backend_type: Type of backend to create ('sqlite', 'postgresql').
3232
If None, reads from settings.storage.backend_type
3333
db_path: Path to database file (SQLite only). If None, uses settings.storage.db_path
34-
connection_string: PostgreSQL connection string (PostgreSQL/Supabase only).
34+
connection_string: PostgreSQL connection string (PostgreSQL only).
3535
If None, builds from settings.storage.postgresql_* settings
3636
3737
Returns:
38-
StorageBackend implementation (SQLiteBackend, PostgreSQLBackend, etc.)
38+
StorageBackend implementation (SQLiteBackend, PostgreSQLBackend)
3939
4040
Raises:
4141
ValueError: If backend_type is invalid or required parameters are missing
@@ -59,10 +59,6 @@ def create_backend(
5959
connection_string='postgresql://user:pass@localhost:5432/dbname',
6060
)
6161
await backend.initialize()
62-
63-
# Create Supabase backend (uses PostgreSQLBackend)
64-
backend = create_backend(backend_type='supabase')
65-
await backend.initialize()
6662
"""
6763
settings = get_settings()
6864

@@ -71,9 +67,9 @@ def create_backend(
7167
backend_type = getattr(settings.storage, 'backend_type', 'sqlite')
7268

7369
# Validate backend type
74-
if backend_type not in ('sqlite', 'postgresql', 'supabase'):
70+
if backend_type not in ('sqlite', 'postgresql'):
7571
raise ValueError(
76-
f'Invalid backend_type: {backend_type}. Must be one of: sqlite, postgresql, supabase',
72+
f'Invalid backend_type: {backend_type}. Must be one of: sqlite, postgresql',
7773
)
7874

7975
# Create appropriate backend
@@ -89,13 +85,6 @@ def create_backend(
8985
# Create SQLite backend
9086
return SQLiteBackend(db_path=db_path)
9187

92-
if backend_type == 'postgresql':
93-
# Create PostgreSQL backend
94-
return PostgreSQLBackend(connection_string=connection_string)
95-
96-
if backend_type == 'supabase':
97-
# Supabase backend uses PostgreSQLBackend with Supabase-specific settings
98-
return PostgreSQLBackend(connection_string=connection_string)
99-
100-
# This should never be reached due to validation above
101-
raise ValueError(f'Unsupported backend_type: {backend_type}')
88+
# backend_type == 'postgresql'
89+
# Create PostgreSQL backend
90+
return PostgreSQLBackend(connection_string=connection_string)

0 commit comments

Comments
 (0)