This document describes the database migration system for nextcloud-mcp-server's token storage database.
The token storage database uses Alembic for schema versioning and migrations. Alembic provides:
- Version Control: Track schema changes in Git
- Rollback Support: Safely downgrade schema if needed
- Audit Trail: Migration files serve as schema changelog
- Automated Upgrades: Database schema updates automatically on startup
The system handles three scenarios:
- New Database: Runs migrations from scratch to create all tables
- Pre-Alembic Database: Stamps existing database with initial revision (no changes)
- Alembic-Managed Database: Upgrades to latest version automatically
nextcloud-mcp-server/
├── alembic/ # Alembic migrations
│ ├── versions/ # Migration scripts
│ │ └── 20251217_2200_001_initial_schema.py
│ ├── env.py # Alembic environment
│ ├── script.py.mako # Migration template
│ └── README # Migration usage guide
├── alembic.ini # Alembic configuration
└── nextcloud_mcp_server/
├── auth/storage.py # Uses migrations on init
└── migrations.py # Migration utilities
Migrations run automatically when the server starts:
uv run nextcloud-mcp-serverThe RefreshTokenStorage.initialize() method:
- Checks if database is Alembic-managed
- Stamps pre-Alembic databases with initial revision
- Upgrades to latest version
# Show current database version
uv run nextcloud-mcp-server db current
# Upgrade database to latest version
uv run nextcloud-mcp-server db upgrade
# Show migration history
uv run nextcloud-mcp-server db history
# Downgrade by one version (emergency use only)
uv run nextcloud-mcp-server db downgrade
# Specify custom database path
uv run nextcloud-mcp-server db current -d /path/to/tokens.dbTOKEN_STORAGE_DB: Path to database file (default:/app/data/tokens.db)
uv run nextcloud-mcp-server db migrate "add user preferences table"This creates a new migration file in alembic/versions/ with empty upgrade() and downgrade() functions.
Since we don't use SQLAlchemy models, write raw SQL:
def upgrade() -> None:
"""Add user preferences table."""
op.execute("""
CREATE TABLE user_preferences (
user_id TEXT PRIMARY KEY,
theme TEXT DEFAULT 'light',
language TEXT DEFAULT 'en',
created_at INTEGER NOT NULL
)
""")
op.execute("""
CREATE INDEX idx_user_preferences_user_id
ON user_preferences(user_id)
""")
def downgrade() -> None:
"""Remove user preferences table."""
op.execute("DROP INDEX IF EXISTS idx_user_preferences_user_id")
op.execute("DROP TABLE IF EXISTS user_preferences")# Test upgrade
uv run nextcloud-mcp-server db upgrade -d /tmp/test.db
# Verify schema
sqlite3 /tmp/test.db ".schema"
# Test downgrade
uv run nextcloud-mcp-server db downgrade -d /tmp/test.db
# Verify removal
sqlite3 /tmp/test.db ".schema"git add alembic/versions/YYYYMMDD_HHMM_XXX_description.py
git commit -m "feat: add user preferences table migration"SQLite has limited ALTER TABLE support:
- ✅ Add columns:
ALTER TABLE table ADD COLUMN ... - ✅ Rename table:
ALTER TABLE old RENAME TO new - ✅ Rename column:
ALTER TABLE table RENAME COLUMN old TO new(SQLite 3.25+)
- ❌ Drop column
- ❌ Change column type
- ❌ Add constraints to existing columns
For complex schema changes:
def upgrade() -> None:
# Create new table with desired schema
op.execute("""
CREATE TABLE refresh_tokens_new (
user_id TEXT PRIMARY KEY,
encrypted_token BLOB NOT NULL,
new_field TEXT, -- New column
expires_at INTEGER,
created_at INTEGER NOT NULL
)
""")
# Copy data from old table
op.execute("""
INSERT INTO refresh_tokens_new
(user_id, encrypted_token, expires_at, created_at)
SELECT user_id, encrypted_token, expires_at, created_at
FROM refresh_tokens
""")
# Drop old table and rename new table
op.execute("DROP TABLE refresh_tokens")
op.execute("ALTER TABLE refresh_tokens_new RENAME TO refresh_tokens")
# Recreate indexes
op.execute("CREATE INDEX idx_user_id ON refresh_tokens(user_id)")- Migrations:
YYYYMMDD_HHMM_XXX_description.py - Revision IDs: Sequential numbers (
001,002,003) - Descriptions: Imperative mood ("add table", "remove column")
- Test Thoroughly: Test both upgrade and downgrade paths
- Preserve Data: Ensure data migration logic is correct
- Document Changes: Add comments explaining complex operations
- Small Changes: One logical change per migration
- No Breaking Changes: Maintain backward compatibility when possible
- Data Loss: Downgrade may lose data (dropped columns, tables)
- Confirmation: Downgrade command requires explicit confirmation
- Testing: Always test downgrade path before deploying
- Emergency Only: Use downgrades only for critical rollbacks
Existing databases created before Alembic integration are automatically detected and stamped with revision 001:
- Server detects no
alembic_versiontable - Checks if
refresh_tokenstable exists - If yes, stamps database with
001(no schema changes) - Future updates use normal migration path
Pre-Alembic DB → Stamp(001) → Upgrade(002) → Upgrade(003) → ...
New DB → Migrate(001) → Upgrade(002) → Upgrade(003) → ...
# Check current state
uv run nextcloud-mcp-server db current -d /path/to/tokens.db
# View migration history
uv run nextcloud-mcp-server db history -d /path/to/tokens.db
# Manually inspect database
sqlite3 /path/to/tokens.db ".schema"WARNING: This destroys all data!
# Downgrade to base (empty database)
uv run nextcloud-mcp-server db downgrade -d /path/to/tokens.db --revision base
# Upgrade to latest
uv run nextcloud-mcp-server db upgrade -d /path/to/tokens.dbIf alembic_version table is corrupted:
# Manually fix via SQL
sqlite3 /path/to/tokens.db
> DELETE FROM alembic_version;
> INSERT INTO alembic_version (version_num) VALUES ('001');
> .quit
# Verify and upgrade
uv run nextcloud-mcp-server db current -d /path/to/tokens.db
uv run nextcloud-mcp-server db upgrade -d /path/to/tokens.db# Run migrations in test environment
export TOKEN_STORAGE_DB=/app/data/tokens.db
uv run nextcloud-mcp-server db upgrade
# Verify current version
uv run nextcloud-mcp-server db currentMigrations run automatically on container startup via RefreshTokenStorage.initialize().
- Stop application
- Backup database:
cp tokens.db tokens.db.backup - Downgrade:
uv run nextcloud-mcp-server db downgrade --revision XXX - Deploy previous application version
- Restart application