MCP SQLite RBAC Demo
README.md
# MCP SQLite RBAC Demo
A production-quality demonstration of a Model Context Protocol (MCP) server that securely exposes a SQLite database to AI agents using Role-Based Access Control (RBAC).
## Overview
This project teaches developers how to build enterprise MCP servers with:
- **Role-Based Access Control (RBAC)** - Three roles (admin, manager, viewer) with granular permissions
- **Authentication & Authorization** - Login/logout with permission enforcement
- **Clean Architecture** - Tools → Services → Repositories → Database
- **Audit Logging** - Track all write operations for compliance
- **Type Safety** - Full type hints with Pydantic schemas
- **Input Validation** - Comprehensive validation at all layers
## Architecture
```
┌─────────────────────────────────────────┐
│ AI Agent / Claude Client │
└────────────────┬────────────────────────┘
│
│ MCP Protocol (JSON-RPC)
▼
┌─────────────────────────────────────────┐
│ MCP Server (FastMCP) │
│ • auth_tools.py │
│ • customer_tools.py │
│ • order_tools.py │
│ • user_tools.py │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Service Layer (Business Logic) │
│ • customer_service.py │
│ • order_service.py │
│ • user_service.py │
│ ├─ Permission checks (require) │
│ ├─ Validation (Pydantic) │
│ └─ Audit logging │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Repository Layer (Data Access) │
│ • customer_repository.py │
│ • order_repository.py │
│ • user_repository.py │
│ └─ SQLAlchemy CRUD operations │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ SQLite Database (db.sqlite3) │
│ • users (id, username, password, role) │
│ • customers (id, name, email, city) │
│ • orders (id, customer_id, product...) │
└─────────────────────────────────────────┘
```
## Database Schema
### Users Table
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password VARCHAR(255),
role VARCHAR(20), -- admin, manager, viewer
created_at DATETIME
)
```
### Customers Table
```sql
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100) UNIQUE,
city VARCHAR(100),
created_at DATETIME,
updated_at DATETIME
)
```
### Orders Table
```sql
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER FOREIGN KEY,
product VARCHAR(200),
amount FLOAT,
status VARCHAR(20), -- Pending, Completed, Cancelled
created_at DATETIME,
updated_at DATETIME
)
```
## Role-Based Permissions
### Viewer Role
- Read-only access to all resources
- **Permissions**: `customer.read`, `order.read`, `user.read`
- **Denied**: All write and delete operations
### Manager Role
- Create and update customers and orders
- Cannot delete resources
- **Permissions**: `customer.read`, `customer.write`, `order.read`, `order.write`, `user.read`
- **Denied**: Delete operations
### Admin Role
- Full access to all resources
- **Permissions**: All operations
## Installation
### Prerequisites
- Python 3.12+
- pip or uv package manager
### Setup
```bash
# Clone the repository
cd mcp-sqlite-rbac-demo
# Install dependencies
pip install -r requirements.txt
# or with uv:
uv pip install -r requirements.txt
# Seed the database with sample data
python seed.py
# Start the MCP server
python server.py
```
## Sample Credentials
```
Admin: username=admin, password=admin123
Manager: username=manager, password=manager123
Viewer: username=viewer, password=viewer123
```
## How MCP Works
Model Context Protocol (MCP) is a standardized interface for LLMs to interact with external tools and data. This server implements MCP by:
1. **Tool Registration** - Server exposes 20+ tools via MCP
2. **JSON-RPC Communication** - Tools are called via JSON-RPC protocol
3. **Authentication** - Tools enforce role-based access control
4. **Structured Input/Output** - All tools have schemas for type safety
### MCP Tool Categories
#### Authentication Tools
- `login(username, password)` - Authenticate user
- `logout()` - End user session
- `whoami()` - Get current user info
- `my_permissions()` - Get user's permissions
#### Customer Tools
- `list_customers(skip=0, limit=100)` - List all customers
- `get_customer(customer_id)` - Get customer by ID
- `search_customers(name, skip=0, limit=100)` - Search by name
- `create_customer(name, email, city)` - Create new customer
- `update_customer(customer_id, name?, email?, city?)` - Update customer
- `delete_customer(customer_id)` - Delete customer
#### Order Tools
- `list_orders(skip=0, limit=100)` - List all orders
- `get_order(order_id)` - Get order by ID
- `get_customer_orders(customer_id, skip=0, limit=100)` - Get customer's orders
- `list_orders_by_status(status, skip=0, limit=100)` - Filter by status
- `create_order(customer_id, product, amount)` - Create new order
- `update_order_status(order_id, status)` - Update order status
- `delete_order(order_id)` - Delete order
#### User Tools
- `list_users(skip=0, limit=100)` - List all users
- `get_user(user_id)` - Get user by ID
## Usage Examples
### Example 1: Viewer Role (Read-Only)
```bash
# 1. Login as viewer
login(username="viewer", password="viewer123")
# Returns: {"username": "viewer", "role": "viewer", ...}
# 2. Check permissions
my_permissions()
# Returns: {"role": "viewer", "permissions": ["customer.read", "order.read", "user.read"]}
# 3. List customers (allowed)
list_customers()
# Returns: {"customers": [...], "count": 3}
# 4. Try to create customer (denied)
create_customer(name="Jane Doe", email="jane@test.com", city="LA")
# Returns: PermissionError: Permission 'customer.write' denied for role 'viewer'
```
### Example 2: Manager Role (Create/Update)
```bash
# 1. Login as manager
login(username="manager", password="manager123")
# 2. Create new customer
create_customer(name="Jane Doe", email="jane@test.com", city="LA")
# Returns: {"id": 4, "name": "Jane Doe", ...}
# 3. Update customer
update_customer(customer_id=4, city="San Francisco")
# Returns: {"id": 4, "name": "Jane Doe", "city": "San Francisco", ...}
# 4. Try to delete customer (denied)
delete_customer(customer_id=4)
# Returns: PermissionError: Permission 'customer.delete' denied for role 'manager'
```
### Example 3: Admin Role (Full Access)
```bash
# 1. Login as admin
login(username="admin", password="admin123")
# 2. List all users
list_users()
# Returns: {"users": [...], "count": 3}
# 3. Delete customer
delete_customer(customer_id=4)
# Returns: {"message": "Customer 4 deleted successfully"}
# 4. All operations allowed
```
## Permission Model
The permission system uses a simple role-to-permission mapping:
```python
PERMISSIONS = {
"admin": {
"user.read", "user.write",
"customer.read", "customer.write", "customer.delete",
"order.read", "order.write", "order.delete",
},
"manager": {
"user.read",
"customer.read", "customer.write",
"order.read", "order.write",
},
"viewer": {
"user.read",
"customer.read",
"order.read",
},
}
```
Every write operation calls `require(role, permission)` which raises `PermissionError` if denied.
## Audit Logging
Every operation is logged to `audit.log` with:
- Timestamp
- Username
- Action (e.g., CREATE_CUSTOMER, LOGIN)
- Resource (e.g., customer:123)
- Result (success or error)
```json
{
"timestamp": "2024-07-30T10:15:30.123456",
"username": "admin",
"action": "CREATE_CUSTOMER",
"resource": "customer:4",
"result": "success"
}
```
## Integrating with AI Clients
### Claude Desktop
Add to Claude Desktop config (`~/.claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"sqlite-rbac": {
"command": "python",
"args": ["/path/to/mcp-sqlite-rbac-demo/server.py"]
}
}
}
```
Then start Claude Desktop and the server will be available.
### OpenAI Agents SDK
```python
import subprocess
from openai import OpenAI
# Start MCP server subprocess
server_process = subprocess.Popen([
"python", "/path/to/server.py"
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
client = OpenAI(api_key="your-key")
# Use MCP server tools with OpenAI
response = client.beta.agents.create(
name="Database Agent",
tools=[
# Tools from MCP server will be available
],
model="gpt-4"
)
```
### LangGraph
```python
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
import subprocess
import json
# Start MCP server
server = subprocess.Popen(["python", "server.py"])
# Define tools that call MCP server
@tool
def list_customers():
"""List all customers"""
# Call MCP server over stdin/stdout
...
# Create LangGraph agent
agent = create_react_agent(ChatOpenAI(model="gpt-4"), [list_customers])
```
### Cursor AI Editor
Add MCP server to Cursor settings (`.cursor/settings.json`):
```json
{
"mcpServers": {
"sqlite-rbac": {
"command": "python",
"args": ["server.py"]
}
}
}
```
## Production Deployment
### PostgreSQL Migration
To migrate from SQLite to PostgreSQL for production:
```python
# 1. Update database URL
settings.database_url = "postgresql://user:password@localhost/rbac_db"
# 2. Change connection settings
engine = create_engine(
settings.database_url,
# Remove SQLite-specific options
# No need for StaticPool with PostgreSQL
)
# 3. Install psycopg2
pip install psycopg2-binary
# 4. Run migrations
alembic upgrade head
# 5. Update connection pooling (optional)
from sqlalchemy.pool import QueuePool
engine = create_engine(
settings.database_url,
poolclass=QueuePool,
pool_size=10,
max_overflow=20,
)
```
### Production Configuration
```python
# config.py
class Settings(BaseSettings):
database_url: str # Read from environment
debug: bool = False
secret_key: str # For session encryption
log_level: str = "INFO"
audit_log_retention_days: int = 90
```
### Security Hardening
1. **Password Hashing** - Replace plaintext passwords with bcrypt:
```python
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
hashed = pwd_context.hash("password")
```
2. **Session Encryption** - Use secure tokens:
```python
import secrets
session_token = secrets.token_urlsafe(32)
```
3. **Rate Limiting** - Limit login attempts:
```python
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
```
4. **HTTPS Only** - Use TLS in production
5. **SQL Injection Prevention** - Already using SQLAlchemy ORM (safe)
## Code Quality
- **Type Hints** - Full type annotations throughout
- **Docstrings** - Every function documented
- **Error Handling** - Friendly error messages
- **Validation** - Pydantic schemas for all inputs
- **Clean Architecture** - Separation of concerns
- **DRY Principle** - No code duplication
## Testing
```bash
# Run example queries (manual testing)
python seed.py # Populate database
# Test permission enforcement
python -c "
from auth import get_session
from services.customer_service import CustomerService
# Login as viewer
session = get_session()
session.login('viewer', 'viewer123')
# Try to create (should fail)
try:
service = CustomerService()
service.create_customer({'name': 'Test'}, session.get_role())
except PermissionError as e:
print(f'✓ Permission denied as expected: {e}')
"
```
## File Structure
```
mcp-sqlite-rbac-demo/
├── README.md # This file
├── requirements.txt # Python dependencies
├── config.py # Configuration management
├── database.py # SQLAlchemy setup
├── models.py # ORM models (User, Customer, Order)
├── schemas.py # Pydantic request/response schemas
├── auth.py # Authentication and session management
├── permissions.py # RBAC permission system
├── audit.py # Audit logging
├── seed.py # Database seeding script
├── server.py # Main MCP server
├── services/
│ ├── customer_service.py # Customer business logic
│ ├── order_service.py # Order business logic
│ └── user_service.py # User business logic
├── repositories/
│ ├── customer_repository.py # Customer data access
│ ├── order_repository.py # Order data access
│ └── user_repository.py # User data access
├── tools/
│ ├── auth_tools.py # MCP tools for auth
│ ├── customer_tools.py # MCP tools for customers
│ ├── order_tools.py # MCP tools for orders
│ └── user_tools.py # MCP tools for users
├── db.sqlite3 # SQLite database (auto-created)
└── audit.log # Audit log entries
```
## Key Concepts Demonstrated
### 1. Clean Architecture
- **Tools Layer** - MCP tool definitions with schemas
- **Service Layer** - Business logic and validation
- **Repository Layer** - Data access and ORM
- **Database Layer** - SQLAlchemy with relationships
### 2. Authentication & Authorization
- Session-based authentication (singleton pattern)
- Role-based access control with granular permissions
- Permission enforcement at service layer
- Audit logging for compliance
### 3. Type Safety
- Pydantic schemas for validation
- SQLAlchemy models for type-safe database access
- Full type hints throughout codebase
### 4. Error Handling
- Custom exceptions (PermissionError, ValueError)
- Friendly error messages
- Audit logging of failures
- Proper HTTP error codes
### 5. Enterprise Patterns
- Dependency injection (services accept db)
- Repository pattern for data access
- Service layer for business logic
- Pagination and filtering
- Relationship management
## Common Patterns
### Permission Check
```python
from permissions import require
def create_customer(data, role):
require(role, "customer.write") # Raises PermissionError if denied
# ... create customer
```
### Service Method
```python
def get_customer(self, customer_id, role):
require(role, "customer.read")
customer = self.repo.get_by_id(customer_id)
if not customer:
raise ValueError(f"Customer {customer_id} not found")
return CustomerResponse.model_validate(customer)
```
### MCP Tool
```python
def list_customers(skip=0, limit=100):
session = get_session()
if not session.is_authenticated():
raise RuntimeError("Not authenticated")
service = CustomerService()
customers = service.list_customers(session.get_role(), skip=skip, limit=limit)
log_audit(session.get_username(), "LIST_CUSTOMERS", "customer", "success")
return {"customers": [c.model_dump() for c in customers]}
```
## Extending the Project
### Add New Resource Type (e.g., Products)
1. **Create Model** (`models.py`):
```python
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
price = Column(Float, nullable=False)
```
2. **Create Repository** (`repositories/product_repository.py`):
```python
class ProductRepository:
def __init__(self, db):
self.db = db
def get_all(self):
return self.db.query(Product).all()
```
3. **Create Service** (`services/product_service.py`):
```python
class ProductService:
def __init__(self, db=None):
self.repo = ProductRepository(db or SessionLocal())
def list_products(self, role):
require(role, "product.read")
return self.repo.get_all()
```
4. **Create Tools** (`tools/product_tools.py`):
```python
def list_products():
session = get_session()
if not session.is_authenticated():
raise RuntimeError("Not authenticated")
service = ProductService()
products = service.list_products(session.get_role())
return {"products": [p.model_dump() for p in products]}
```
5. **Register in Server** (`server.py`):
```python
from tools.product_tools import get_product_tools
all_tools = [...] + get_product_tools()
```
### Add New Role (e.g., Analyst)
1. **Update Permissions** (`permissions.py`):
```python
PERMISSIONS = {
# ...
"analyst": {
"customer.read",
"order.read",
"report.read",
},
}
```
2. **Seed Users** (`seed.py`):
```python
User(username="analyst", password="analyst123", role="analyst")
```
## License
This project is provided as educational material for learning MCP server development.
## Support
For questions or issues, refer to:
- [MCP Documentation](https://modelcontextprotocol.io)
- [FastMCP SDK](https://github.com/jlowin/FastMCP)
- [SQLAlchemy Docs](https://docs.sqlalchemy.org)
- [Pydantic Docs](https://docs.pydantic.dev)
## Troubleshooting
### Database locked error
- SQLite can be slow with concurrent access
- For production, use PostgreSQL instead
### Permission denied errors
- Verify you're logged in with `whoami()`
- Check your permissions with `my_permissions()`
- Use a higher-role account (manager, admin)
### No tables in database
- Run `python seed.py` to initialize the database
- Check that `db.sqlite3` was created
### Tool not found
- Ensure all dependencies are installed: `pip install -r requirements.txt`
- Restart the server after modifying tools
- Check server logs for errors
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues