Model Context Protocol (MCP)
MCP server for AI assistants with auto-generated database tools, JSON-RPC 2.0 transport, SSE streaming, per-project configuration, and RLS enforcement.
Model Context Protocol (MCP)
The atlas-mcp crate turns every AltBase project into an MCP-compatible tool provider for AI assistants like Claude, ChatGPT, Cursor, and any client that supports the Model Context Protocol. One URL and one API key give an AI agent the ability to query your database, inspect schemas, and perform CRUD operations with full row-level security enforcement.
Overview
MCP is an open standard for connecting AI models to external tools and data sources. AltBase implements MCP as a native Rust server that auto-generates tools from your database schema with zero configuration. Every non-excluded table gets query_, insert_, update_, and delete_ tools, plus global tools for raw SQL (run_sql), schema inspection (describe_schema), and table listing (list_tables).
The server supports the MCP 2025-03-26 Streamable HTTP transport (POST for JSON-RPC, GET for SSE streaming) and a legacy SSE endpoint for pre-2025 clients. All database operations run with tenant schema isolation via SET LOCAL search_path and enforce PostgreSQL row-level security based on the API key role.
Key Concepts
Auto-generated tools are created for every table that is not excluded in the project's MCP config. The tool names follow the pattern query_{table}, insert_{table}, update_{table}, delete_{table}. Tool input schemas include filter operators, column selection, ordering, and pagination. Update and delete tools require at least one filter to prevent full-table mutations.
Global tools are always available regardless of table configuration. run_sql executes parameterized SQL (service key only), describe_schema returns full column definitions with relationships and indexes, and list_tables returns all tables with column counts.
JSON-RPC 2.0 is the wire protocol. Every MCP message is a JSON-RPC request or response. The client sends tools/list to discover available tools and tools/call to invoke them. Tool results are returned as content blocks with structured JSON inside a text field.
Streamable HTTP transport uses a single /mcp endpoint. POST /mcp handles JSON-RPC requests and responds with application/json or text/event-stream. GET /mcp opens an SSE stream for server-push notifications.
Legacy SSE transport at /mcp/sse supports pre-2025 MCP clients. The client opens an SSE connection and receives an endpoint event with a POST URL for sending JSON-RPC messages.
MCP lifecycle handshake is required before any tool calls. The client sends an initialize request with its protocol version and capabilities. The server responds with its capabilities (tools, resources). The client then sends a notifications/initialized notification. Methods sent before initialization return JSON-RPC error -32600.
Per-project configuration controls which tools are exposed, which tables are excluded, and whether write operations are allowed. The config also supports opt-in modules for storage, auth, functions, and search tools.
Permission mapping ties tool visibility to the API key role. Anon keys see read-only tools (query_*, describe_schema, list_tables). Authenticated keys (JWT) see CRUD tools with RLS enforcement. Service keys see all tools including run_sql and module tools.
Query result limits cap results at 100 rows per tool call. Responses include has_more and next_offset for pagination. The AI agent can call the tool again with the offset to fetch more.
API Reference
MCP Protocol Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
POST | /mcp | JSON-RPC 2.0 endpoint (Streamable HTTP) | API Key |
GET | /mcp | SSE stream for server-push notifications | API Key |
DELETE | /mcp | Terminate session (returns 200 or 405 if stateless) | API Key |
GET | /mcp/sse | Legacy SSE endpoint for pre-2025 clients | API Key |
Configuration Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /v1/projects/{id}/mcp/config | Get MCP configuration | Service Key |
PATCH | /v1/projects/{id}/mcp/config | Update MCP configuration | Service Key |
Per-Table Tools (Auto-Generated)
| Tool Name | Description | Input Schema |
|---|---|---|
query_{table} | Query rows with filters, ordering, pagination | { filters?, select?, order?, limit?, offset? } |
insert_{table} | Insert one or more rows | { rows: [{ col: val }] } |
update_{table} | Update rows matching filter (filter required) | { filters: { col: { op: val } }, set: { col: val } } |
delete_{table} | Delete rows matching filter (filter required) | { filters: { col: { op: val } } } |
Global Tools
| Tool Name | Description | Auth Required |
|---|---|---|
run_sql | Execute parameterized SQL (query, params[]) | Service Key only |
describe_schema | Full database schema with relationships and indexes | Any API Key |
list_tables | List all tables with column counts | Any API Key |
Filter Operators
| JSON Operator | SQL | Example |
|---|---|---|
eq | = $N | {"status": {"eq": "active"}} |
neq | != $N | {"role": {"neq": "admin"}} |
gt | > $N | {"price": {"gt": 100}} |
gte | >= $N | {"age": {"gte": 18}} |
lt | < $N | {"price": {"lt": 50}} |
lte | <= $N | {"count": {"lte": 10}} |
like | LIKE $N | {"name": {"like": "%widget%"}} |
ilike | ILIKE $N | {"email": {"ilike": "%@gmail%"}} |
in | IN ($N,...) | {"status": {"in": ["active", "pending"]}} |
is | IS $N | {"deleted_at": {"is": null}} |
Multiple operators on the same column are ANDed: {"price": {"gte": 10, "lt": 100}} produces price >= 10 AND price < 100.
Permission Mapping
| API Key Role | Visible Tools | run_sql Access |
|---|---|---|
| Anon | query_*, describe_schema, list_tables | No |
| Authenticated (JWT) | query_*, insert_*, update_*, delete_* (RLS enforced) | No |
| Custom role | Per-table tools matching the role's permissions | No |
| Service | All tools including module tools | Yes |
MCP Resources (Read-Only Context)
| URI Pattern | Description |
|---|---|
schema://tables | List of all tables with column summaries |
schema://tables/{name} | Full column definitions, relationships, and indexes |
schema://enums | All enum types in the schema |
data://{table}/sample | First 5 rows for context (respects RLS, max 100 rows) |
Code Examples
Connect from Claude Desktop or Cursor
Add to your MCP client configuration:
{
"mcpServers": {
"my-saas-db": {
"transport": {
"type": "streamable-http",
"url": "https://my-project.atlasdb.io/mcp",
"headers": {
"Authorization": "Bearer atls_sk_abc123..."
}
}
}
}
}
MCP lifecycle handshake
# 1. Initialize
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "my-agent", "version": "1.0"}
}
}'
# 2. Send initialized notification
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "notifications/initialized"}'
List available tools
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'
Query a table
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "query_products",
"arguments": {
"filters": {"price": {"lt": 100}},
"select": ["id", "name", "price"],
"order": "price",
"limit": 10
}
}
}'
# Response content: {"rows": [...], "count": 5, "has_more": false}
Configure MCP for a project
# Get current config
curl http://localhost:3000/v1/projects/$PROJECT_ID/mcp/config \
-H "Authorization: Bearer $SERVICE_KEY"
# Make MCP read-only (disable write tools)
curl -X PATCH http://localhost:3000/v1/projects/$PROJECT_ID/mcp/config \
-H "Authorization: Bearer $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"auto_server": {"read_only": true}}'
# Exclude sensitive tables
curl -X PATCH http://localhost:3000/v1/projects/$PROJECT_ID/mcp/config \
-H "Authorization: Bearer $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"auto_server": {"exclude_tables": ["secrets", "api_keys", "internal_logs"]}}'
# Enable the storage module
curl -X PATCH http://localhost:3000/v1/projects/$PROJECT_ID/mcp/config \
-H "Authorization: Bearer $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"auto_server": {"modules": {"storage": true}}}'
Configuration
Per-Project MCP Config
The MCP config is stored in the control plane, per-project. The default configuration enables the database module only, excludes internal tables, and allows read-write operations.
{
"enabled": true,
"auto_server": {
"enabled": true,
"modules": {
"database": true,
"storage": false,
"auth": false,
"functions": false,
"search": false
},
"exclude_tables": [
"_migrations", "rls_policies", "users", "sessions",
"refresh_tokens", "verification_tokens", "oauth_providers",
"sso_connections", "user_identities", "customer_orgs"
],
"read_only": false
}
}
Opt-In Module Tools
| Module | Config Key | Tools Added |
|---|---|---|
storage | modules.storage: true | upload_file, download_file, list_files, delete_file, create_bucket |
auth | modules.auth: true | list_users, get_user, create_user, update_user, delete_user |
functions | modules.functions: true | invoke_function, list_functions |
search | modules.search: true | vector_search, fulltext_search, hybrid_search, ask_knowledge |
Origin Header Validation
Per the MCP 2025-03-26 spec, the server validates the Origin header on all incoming requests to prevent DNS rebinding attacks. If the Origin header is present, it must be in the project's configured allowed origins. Server-to-server requests without an Origin header are allowed (API key auth is sufficient). The default allowlist is empty, meaning all browser origins are rejected unless explicitly configured.
Transport Details
| Transport | Endpoint | Protocol Version | Notes |
|---|---|---|---|
| Streamable HTTP | POST/GET /mcp | 2025-03-26 | Primary transport. Single endpoint for request/response and server-push. |
| Legacy SSE | GET /mcp/sse | 2024-11-05 | Backward compat. Client receives endpoint event, then POSTs JSON-RPC to that URL. |
Session management is stateless in v1. No Mcp-Session-Id is issued. Each POST is independent. The initialize handshake is idempotent.
How It Works
MCP Request Flow
- The AI assistant sends a JSON-RPC 2.0 request to
POST /mcpwith anAuthorizationheader containing an API key. - The
api_key_middlewareauthenticates the request and injectsTenantContextwith the project ID, schema name, and tier. - The protocol layer parses the JSON-RPC message and identifies the method (
initialize,tools/list,tools/call, etc.). - For
tools/list: the server introspects the tenant database schema (using the cached schema fromTieredCache), generates tool definitions for each non-excluded table based on the API key's role, and returns the tool list. - For
tools/call: the server validates the tool name against the project's MCP config and the API key's permissions, then dispatches to the executor. - The executor starts a PostgreSQL transaction, sets
search_pathto the tenant schema, setsstatement_timeoutbased on the tier, and runs the operation (SELECT, INSERT, UPDATE, or DELETE) using the existing query builder fromatlas-api-engine. - Results are formatted as MCP content blocks:
{"rows": [...], "count": N, "has_more": bool, "next_offset": N}inside a text content field. - The JSON-RPC response is returned to the AI assistant.
SQL Execution and Tenant Isolation
All MCP database operations execute within a PostgreSQL transaction with tenant isolation:
SET LOCAL search_path TO "proj_{project_id}"ensures all queries run against the correct tenant schema.SET LOCAL statement_timeout = '{tier_timeout}'prevents runaway queries based on the plan tier.- RLS context is injected via
SET LOCAL(role from API key, JWT claims if present). - The query runs using the existing
build_select,build_insert,build_update, orbuild_deletefunctions fromatlas-api-engine. - The transaction commits and results are returned.
Tool Generation from Schema
- On the first MCP request for a project (or after schema changes), the server calls schema introspection to get
SchemaInfowith all tables and columns. - Internal tables (those in
exclude_tables) and system tables (without aworkspace_idcolumn) are filtered out. - For each remaining table, four tools are generated:
query_{table},insert_{table},update_{table},delete_{table}. - Tool input schemas are derived from the table's column types, with filter operators mapped to the existing
FilterOpAST inatlas-api-engine. - If
read_only: trueis set in the config, onlyquery_{table}tools are generated. - The tool definitions are cached alongside the schema and invalidated when the schema changes.
SSE Streaming
- The AI client connects to
GET /mcpwithAccept: text/event-stream. - The server sends initialization events including server info and capabilities.
- The client sends tool calls via the SSE channel.
- The server executes each tool call and streams results as SSE events.
- The connection remains open for multiple interactions until the client disconnects.
Analytics & Billing
Plan tiers with resource limits, Stripe subscription management, usage tracking with 30-day rolling windows, materialized views for analytics, and webhook-driven tier changes.
JavaScript / TypeScript SDK
Install and use the AltBase JS/TS SDK to query data, authenticate users, manage files, subscribe to realtime events, invoke functions, search, and more.