#
tokens: 12348/50000 34/34 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── docker
│   └── entrypoint.sh
├── docker-commands.json
├── Dockerfile
├── migationsenv
│   ├── bin
│   │   ├── activate
│   │   ├── activate.csh
│   │   ├── activate.fish
│   │   ├── Activate.ps1
│   │   ├── coverage
│   │   ├── coverage-3.12
│   │   ├── coverage3
│   │   ├── django-admin
│   │   ├── dotenv
│   │   ├── httpx
│   │   ├── mcp
│   │   ├── pip
│   │   ├── pip3
│   │   ├── pip3.12
│   │   ├── py.test
│   │   ├── pytest
│   │   ├── python
│   │   ├── python3
│   │   ├── python3.12
│   │   ├── sqlformat
│   │   └── uvicorn
│   └── pyvenv.cfg
├── migrations_mcp
│   ├── __init__.py
│   ├── handlers
│   │   ├── __init__.py
│   │   └── utils.py
│   ├── service.py
│   └── tests
│       ├── __init__.py
│       └── test_handlers.py
├── pytest.ini
├── README.md
├── requirements.txt
├── setup.py
└── testproject
    ├── manage 2.py
    ├── manage.py
    ├── testapp
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   ├── __init__.py
    │   │   └── 0001_initial.py
    │   ├── models.py
    │   ├── tests
    │   │   ├── __init__.py
    │   │   ├── conftest.py
    │   │   └── test_migrations.py
    │   ├── tests.py
    │   └── views.py
    └── testproject
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual Environment
venv/
env/
ENV/
.env
.venv
env.bak/
venv.bak/

# Django
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
media/
staticfiles/

# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.project
.pydevproject
.settings/

# Testing
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
htmlcov/

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Docker
.docker/
docker-compose.override.yml

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# System Files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Environment Variables
.env
.env.local
.env.*.local

# Migration files (optional, uncomment if you want to ignore migrations)
# */migrations/*.py
# !*/migrations/__init__.py

# Documentation
/site
docs/_build/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# Temporary files
*.bak
*.tmp
*.temp 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/mrrobotke-django-migrations-mcp-badge.png)](https://mseep.ai/app/mrrobotke-django-migrations-mcp)

# Django Migrations MCP Service

A Model Context Protocol (MCP) service for managing Django migrations in distributed environments. This service wraps Django's migration commands and exposes them as MCP endpoints, making it easy to manage migrations across multiple services and integrate with CI/CD pipelines.

## Features

- Check migration status (equivalent to `showmigrations`)
- Create new migrations with validation (equivalent to `makemigrations`)
- Apply migrations with safety checks (equivalent to `migrate`)
- Additional validation and safety checks:
  - Sequential migration order verification
  - Conflict detection
  - Dependency validation
  - Safety analysis of migration operations

## Installation

### Local Development

1. Clone the repository:
```bash
git clone https://github.com/mrrobotke/django-migrations-mcp.git
cd django-migrations-mcp
```

2. Install dependencies:
```bash
pip install -r requirements.txt
```

## Configuration

Set the following environment variables:

```bash
export DJANGO_SETTINGS_MODULE="your_project.settings"
export MCP_SERVICE_PORT=8000  # Optional, defaults to 8000
```

## Usage

### Running the Service

1. Directly with Python:
```bash
python -m migrations_mcp.service
```

2. Using Docker:
```bash
docker build -t django-migrations-mcp .
docker run -e DJANGO_SETTINGS_MODULE=your_project.settings \
          -v /path/to/your/django/project:/app/project \
          -p 8000:8000 \
          django-migrations-mcp
```

### MCP Endpoints

1. Show Migrations:
```python
from mcp import MCPClient

client = MCPClient()
migrations = await client.call("show_migrations")
```

2. Make Migrations:
```python
result = await client.call("make_migrations", {
    "app_labels": ["myapp"],  # Optional
    "dry_run": True  # Optional
})
```

3. Apply Migrations:
```python
result = await client.call("migrate", {
    "app_label": "myapp",  # Optional
    "migration_name": "0001",  # Optional
    "fake": False,  # Optional
    "plan": True  # Optional
})
```

## CI/CD Integration

Example GitHub Actions workflow:

```yaml
name: Django Migrations Check

on:
  pull_request:
    paths:
      - '*/migrations/*.py'
      - '*/models.py'

jobs:
  check-migrations:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
    
    - name: Start MCP service
      run: |
        python -m migrations_mcp.service &
    
    - name: Check migrations
      run: |
        python ci/check_migrations.py
```

Example check_migrations.py script:

```python
import asyncio
from mcp import MCPClient

async def check_migrations():
    client = MCPClient()
    
    # Check current status
    migrations = await client.call("show_migrations")
    
    # Try making migrations
    result = await client.call("make_migrations", {"dry_run": True})
    if not result.success:
        print(f"Error: {result.message}")
        exit(1)
    
    print("Migration check passed!")

if __name__ == "__main__":
    asyncio.run(check_migrations())
```

## Development

### Running Tests

```bash
pytest migrations_mcp/tests/
```

### Code Style

The project follows PEP 8 guidelines. Format your code using:

```bash
black migrations_mcp/
isort migrations_mcp/
```

## License

MIT License. See LICENSE file for details.

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## Docker Usage

The project includes a `docker-commands.json` file that provides structured commands for different deployment scenarios. You can use these commands directly or parse them in your scripts.

### Available Docker Configurations

1. **Redis MCP Server**
```bash
# Run Redis MCP server
docker run -i --rm mcp/redis redis://host.docker.internal:6379
```

2. **Django Migrations MCP Server**
```bash
# Basic setup
docker run -d \
  --name django-migrations-mcp \
  -e DJANGO_SETTINGS_MODULE=your_project.settings \
  -e MCP_SERVICE_PORT=8000 \
  -v /path/to/your/django/project:/app/project \
  -p 8000:8000 \
  django-migrations-mcp

# With Redis integration
docker run -d \
  --name django-migrations-mcp \
  -e DJANGO_SETTINGS_MODULE=your_project.settings \
  -e MCP_SERVICE_PORT=8000 \
  -e REDIS_URL=redis://host.docker.internal:6379 \
  -v /path/to/your/django/project:/app/project \
  -p 8000:8000 \
  --network host \
  django-migrations-mcp
```

3. **Development Environment**
```bash
# Using docker-compose
docker-compose up -d --build
```

4. **Testing Environment**
```bash
# Run tests in container
docker run --rm \
  -e DJANGO_SETTINGS_MODULE=your_project.settings \
  -e PYTHONPATH=/app \
  -v ${PWD}:/app \
  django-migrations-mcp \
  pytest
```

5. **Production Environment**
```bash
# Production setup with health check
docker run -d \
  --name django-migrations-mcp \
  -e DJANGO_SETTINGS_MODULE=your_project.settings \
  -e MCP_SERVICE_PORT=8000 \
  -e REDIS_URL=redis://your-redis-host:6379 \
  -v /path/to/your/django/project:/app/project \
  -p 8000:8000 \
  --restart unless-stopped \
  --network your-network \
  django-migrations-mcp
```

### Using the Commands Programmatically

You can parse and use the commands programmatically:

```python
import json
import subprocess

# Load commands
with open('docker-commands.json') as f:
    commands = json.load(f)

# Run Redis MCP server
redis_config = commands['mcpServers']['redis']
subprocess.run([redis_config['command']] + redis_config['args'])

# Run Django Migrations MCP server
django_config = commands['mcpServers']['djangoMigrations']
subprocess.run([django_config['command']] + django_config['args'])
```

### Network Setup

1. **Development Network**
```bash
docker network create mcp-dev-network
```

2. **Production Network**
```bash
docker network create --driver overlay --attachable mcp-prod-network
```

### Using MCP Tools

The service exposes several endpoints that can be accessed via curl or any HTTP client:

1. **Show Migrations**
```bash
curl -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -d '{"method": "show_migrations"}'
```

2. **Make Migrations**
```bash
curl -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -d '{"method": "make_migrations", "params": {"apps": ["your_app"]}}'
```

3. **Apply Migrations**
```bash
curl -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -d '{"method": "migrate", "params": {"app": "your_app"}}'
``` 
```

--------------------------------------------------------------------------------
/migrations_mcp/handlers/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/migrations_mcp/tests/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/testproject/testapp/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/testproject/testapp/migrations/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/testproject/testapp/tests/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/testproject/testproject/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/testproject/testapp/tests.py:
--------------------------------------------------------------------------------

```python
from django.test import TestCase

# Create your tests here.

```

--------------------------------------------------------------------------------
/testproject/testapp/admin.py:
--------------------------------------------------------------------------------

```python
from django.contrib import admin

# Register your models here.

```

--------------------------------------------------------------------------------
/testproject/testapp/views.py:
--------------------------------------------------------------------------------

```python
from django.shortcuts import render

# Create your views here.

```

--------------------------------------------------------------------------------
/migrations_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
"""Django migrations MCP package."""

from .service import DjangoMigrationsMCP

__all__ = ['DjangoMigrationsMCP']

```

--------------------------------------------------------------------------------
/testproject/testapp/apps.py:
--------------------------------------------------------------------------------

```python
from django.apps import AppConfig


class TestappConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "testapp"

```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
Django>=5.1.0
mcp
python-dotenv>=1.0.0
pydantic>=2.0.0
structlog>=23.1.0
pytest>=7.3.1
pytest-asyncio>=0.21.0
pytest-cov>=4.1.0
black>=23.7.0
isort>=5.12.0
mypy>=1.4.1
pylint>=2.17.5 
```

--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------

```
[pytest]
DJANGO_SETTINGS_MODULE = testproject.settings
python_paths = .
testpaths = testproject/testapp/tests
filterwarnings =
    ignore::DeprecationWarning
    ignore::UserWarning
addopts = -v --tb=short
asyncio_mode = auto
asyncio_default_fixture_loop_scope = function 
```

--------------------------------------------------------------------------------
/testproject/testapp/models.py:
--------------------------------------------------------------------------------

```python
from django.db import models

# Create your models here.

class TestModel(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

```

--------------------------------------------------------------------------------
/testproject/testproject/asgi.py:
--------------------------------------------------------------------------------

```python
"""
ASGI config for testproject project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings")

application = get_asgi_application()

```

--------------------------------------------------------------------------------
/testproject/testproject/wsgi.py:
--------------------------------------------------------------------------------

```python
"""
WSGI config for testproject project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings")

application = get_wsgi_application()

```

--------------------------------------------------------------------------------
/docker/entrypoint.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
set -e

# Validate environment variables
if [ -z "$DJANGO_SETTINGS_MODULE" ]; then
    echo "Error: DJANGO_SETTINGS_MODULE environment variable is required"
    exit 1
fi

# Wait for database if needed (uncomment and modify as needed)
# until nc -z $DB_HOST $DB_PORT; do
#     echo "Waiting for database..."
#     sleep 1
# done

# Start the MCP service
exec python -m migrations_mcp.service 
```

--------------------------------------------------------------------------------
/testproject/testapp/tests/conftest.py:
--------------------------------------------------------------------------------

```python
"""Test configuration for pytest."""
import pytest
from django.conf import settings

@pytest.fixture(scope="function")
def enable_db_access_for_all_tests(db):
    """Enable database access for all tests."""
    pass

@pytest.fixture(scope="session")
def django_db_setup():
    """Configure Django database for tests."""
    settings.DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': ':memory:',
        }
    } 
```

--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------

```python
from setuptools import setup, find_packages

setup(
    name="migrations-mcp",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "Django>=5.1.0",
        "mcp",
        "python-dotenv>=1.0.0",
        "pydantic>=2.0.0",
        "structlog>=23.1.0",
    ],
    extras_require={
        "dev": [
            "pytest>=7.3.1",
            "pytest-asyncio>=0.21.0",
            "pytest-cov>=4.1.0",
            "black>=23.7.0",
            "isort>=5.12.0",
            "mypy>=1.4.1",
            "pylint>=2.17.5",
        ]
    },
) 
```

--------------------------------------------------------------------------------
/testproject/manage 2.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/testproject/manage.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/testproject/testproject/urls.py:
--------------------------------------------------------------------------------

```python
"""
URL configuration for testproject project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path("admin/", admin.site.urls),
]

```

--------------------------------------------------------------------------------
/testproject/testapp/migrations/0001_initial.py:
--------------------------------------------------------------------------------

```python
# Generated by Django 5.1.6 on 2025-02-11 21:07

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = []

    operations = [
        migrations.CreateModel(
            name="TestModel",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("name", models.CharField(max_length=100)),
                ("description", models.TextField(blank=True)),
                ("created_at", models.DateTimeField(auto_now_add=True)),
                ("updated_at", models.DateTimeField(auto_now=True)),
            ],
        ),
    ]

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Build stage
FROM python:3.11-slim as builder

WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt

# Final stage
FROM python:3.11-slim

WORKDIR /app

# Create non-root user
RUN useradd -m -u 1000 mcp && \
    chown -R mcp:mcp /app

# Copy wheels from builder
COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

# Install dependencies
RUN pip install --no-cache /wheels/*

# Copy application code
COPY migrations_mcp ./migrations_mcp
COPY docker/entrypoint.sh .

# Set permissions
RUN chmod +x entrypoint.sh && \
    chown -R mcp:mcp /app

USER mcp

# Environment variables
ENV PYTHONPATH=/app
ENV DJANGO_SETTINGS_MODULE=""
ENV MCP_SERVICE_PORT=8000

# Expose port
EXPOSE 8000

# Run the service
ENTRYPOINT ["./entrypoint.sh"] 
```

--------------------------------------------------------------------------------
/testproject/testapp/tests/test_migrations.py:
--------------------------------------------------------------------------------

```python
"""Test Django migrations with MCP service."""
import os
import pytest
from migrations_mcp.service import DjangoMigrationsMCP

# Configure Django settings for tests
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproject.settings')

@pytest.fixture
def service():
    """Create a DjangoMigrationsMCP service instance."""
    return DjangoMigrationsMCP()

@pytest.mark.django_db
@pytest.mark.asyncio
async def test_show_migrations(service):
    """Test show_migrations command."""
    result = await service.show_migrations()
    assert isinstance(result, list)
    # At least one migration should exist (initial migration)
    assert len(result) > 0

@pytest.mark.django_db
@pytest.mark.asyncio
async def test_make_migrations(service):
    """Test make_migrations command."""
    result = await service.make_migrations(['testapp'])
    assert result.success
    assert "Migrations created successfully" in result.message

@pytest.mark.django_db
@pytest.mark.asyncio
async def test_migrate(service):
    """Test migrate command."""
    result = await service.migrate('testapp')
    assert result.success
    assert "Migrations applied successfully" in result.message 
```

--------------------------------------------------------------------------------
/migationsenv/bin/activate.fish:
--------------------------------------------------------------------------------

```
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/). You cannot run it directly.

function deactivate  -d "Exit virtual environment and return to normal shell environment"
    # reset old environment variables
    if test -n "$_OLD_VIRTUAL_PATH"
        set -gx PATH $_OLD_VIRTUAL_PATH
        set -e _OLD_VIRTUAL_PATH
    end
    if test -n "$_OLD_VIRTUAL_PYTHONHOME"
        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
        set -e _OLD_VIRTUAL_PYTHONHOME
    end

    if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
        set -e _OLD_FISH_PROMPT_OVERRIDE
        # prevents error when using nested fish instances (Issue #93858)
        if functions -q _old_fish_prompt
            functions -e fish_prompt
            functions -c _old_fish_prompt fish_prompt
            functions -e _old_fish_prompt
        end
    end

    set -e VIRTUAL_ENV
    set -e VIRTUAL_ENV_PROMPT
    if test "$argv[1]" != "nondestructive"
        # Self-destruct!
        functions -e deactivate
    end
end

# Unset irrelevant variables.
deactivate nondestructive

set -gx VIRTUAL_ENV "/Users/antonyngigge/Library/Mobile Documents/com~apple~CloudDocs/iworldafric/djangomigrationsmcp/migationsenv"

set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH

# Unset PYTHONHOME if set.
if set -q PYTHONHOME
    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
    set -e PYTHONHOME
end

if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
    # fish uses a function instead of an env var to generate the prompt.

    # Save the current fish_prompt function as the function _old_fish_prompt.
    functions -c fish_prompt _old_fish_prompt

    # With the original prompt function renamed, we can override with our own.
    function fish_prompt
        # Save the return status of the last command.
        set -l old_status $status

        # Output the venv prompt; color taken from the blue of the Python logo.
        printf "%s%s%s" (set_color 4B8BBE) "(migationsenv) " (set_color normal)

        # Restore the return status of the previous command.
        echo "exit $old_status" | .
        # Output the original/"old" prompt.
        _old_fish_prompt
    end

    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
    set -gx VIRTUAL_ENV_PROMPT "(migationsenv) "
end

```

--------------------------------------------------------------------------------
/migrations_mcp/service.py:
--------------------------------------------------------------------------------

```python
"""
Django Migrations MCP Service.
This service provides endpoints for managing Django migrations through MCP.
"""
import asyncio
import logging
from typing import Any, Dict, List, Optional

from django.core.management import call_command
from django.db import connection
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.tools.base import Tool
from pydantic import BaseModel
from asgiref.sync import sync_to_async

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class MigrationStatus(BaseModel):
    """Model representing migration status."""
    app: str
    name: str
    applied: bool
    dependencies: List[str] = []

class MigrationResult(BaseModel):
    """Model representing migration operation result."""
    success: bool
    message: str
    details: Optional[Dict[str, Any]] = None

class DjangoMigrationsMCP(FastMCP):
    """MCP service for managing Django migrations."""

    async def show_migrations(self) -> List[str]:
        """Show all migrations."""
        try:
            @sync_to_async
            def _show_migrations():
                with connection.cursor() as cursor:
                    call_command('showmigrations', stdout=cursor)
                    return cursor.fetchall()
            return await _show_migrations()
        except Exception as e:
            return [f"Error showing migrations: {str(e)}"]

    show_migrations_tool = Tool.from_function(show_migrations)

    async def make_migrations(self, apps: Optional[List[str]] = None) -> MigrationResult:
        """Make migrations for specified apps or all apps."""
        try:
            @sync_to_async
            def _make_migrations():
                call_command('makemigrations', *apps if apps else [])
            await _make_migrations()
            return MigrationResult(
                success=True,
                message="Migrations created successfully"
            )
        except Exception as e:
            return MigrationResult(
                success=False,
                message=f"Error creating migrations: {str(e)}"
            )

    make_migrations_tool = Tool.from_function(make_migrations)

    async def migrate(self, app: Optional[str] = None) -> MigrationResult:
        """Apply migrations for specified app or all apps."""
        try:
            @sync_to_async
            def _migrate():
                call_command('migrate', app if app else '')
            await _migrate()
            return MigrationResult(
                success=True,
                message="Migrations applied successfully"
            )
        except Exception as e:
            return MigrationResult(
                success=False,
                message=f"Error applying migrations: {str(e)}"
            )

    migrate_tool = Tool.from_function(migrate)

if __name__ == "__main__":
    service = DjangoMigrationsMCP()
    service.run() 
```

--------------------------------------------------------------------------------
/testproject/testproject/settings.py:
--------------------------------------------------------------------------------

```python
"""
Django settings for testproject project.

Generated by 'django-admin startproject' using Django 5.1.5.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-test-key-for-development-only'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "testapp",  # Add our test app
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "testproject.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "testproject.wsgi.application"


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

```

--------------------------------------------------------------------------------
/docker-commands.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "redis": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "mcp/redis",
        "redis://host.docker.internal:6379"
      ]
    },
    "djangoMigrations": {
      "command": "docker",
      "args": [
        "run",
        "-d",
        "--name", "django-migrations-mcp",
        "-e", "DJANGO_SETTINGS_MODULE=your_project.settings",
        "-e", "MCP_SERVICE_PORT=8000",
        "-v", "/path/to/your/django/project:/app/project",
        "-p", "8000:8000",
        "django-migrations-mcp"
      ]
    },
    "djangoMigrationsWithRedis": {
      "command": "docker",
      "args": [
        "run",
        "-d",
        "--name", "django-migrations-mcp",
        "-e", "DJANGO_SETTINGS_MODULE=your_project.settings",
        "-e", "MCP_SERVICE_PORT=8000",
        "-e", "REDIS_URL=redis://host.docker.internal:6379",
        "-v", "/path/to/your/django/project:/app/project",
        "-p", "8000:8000",
        "--network", "host",
        "django-migrations-mcp"
      ]
    },
    "development": {
      "command": "docker-compose",
      "args": [
        "up",
        "-d",
        "--build"
      ],
      "environment": {
        "DJANGO_SETTINGS_MODULE": "your_project.settings",
        "MCP_SERVICE_PORT": "8000",
        "REDIS_URL": "redis://localhost:6379"
      }
    },
    "testing": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-e", "DJANGO_SETTINGS_MODULE=your_project.settings",
        "-e", "PYTHONPATH=/app",
        "-v", "${PWD}:/app",
        "django-migrations-mcp",
        "pytest"
      ]
    },
    "production": {
      "command": "docker",
      "args": [
        "run",
        "-d",
        "--name", "django-migrations-mcp",
        "-e", "DJANGO_SETTINGS_MODULE=your_project.settings",
        "-e", "MCP_SERVICE_PORT=8000",
        "-e", "REDIS_URL=redis://your-redis-host:6379",
        "-v", "/path/to/your/django/project:/app/project",
        "-p", "8000:8000",
        "--restart", "unless-stopped",
        "--network", "your-network",
        "django-migrations-mcp"
      ],
      "healthCheck": {
        "test": ["CMD", "curl", "-f", "http://localhost:8000/health"],
        "interval": "30s",
        "timeout": "10s",
        "retries": 3
      }
    }
  },
  "tools": {
    "showMigrations": {
      "command": "curl",
      "args": [
        "-X", "POST",
        "http://localhost:8000/mcp",
        "-H", "Content-Type: application/json",
        "-d", "{\"method\": \"show_migrations\"}"
      ]
    },
    "makeMigrations": {
      "command": "curl",
      "args": [
        "-X", "POST",
        "http://localhost:8000/mcp",
        "-H", "Content-Type: application/json",
        "-d", "{\"method\": \"make_migrations\", \"params\": {\"apps\": [\"your_app\"]}}"
      ]
    },
    "migrate": {
      "command": "curl",
      "args": [
        "-X", "POST",
        "http://localhost:8000/mcp",
        "-H", "Content-Type: application/json",
        "-d", "{\"method\": \"migrate\", \"params\": {\"app\": \"your_app\"}}"
      ]
    }
  },
  "networks": {
    "development": {
      "command": "docker",
      "args": [
        "network",
        "create",
        "mcp-dev-network"
      ]
    },
    "production": {
      "command": "docker",
      "args": [
        "network",
        "create",
        "--driver", "overlay",
        "--attachable",
        "mcp-prod-network"
      ]
    }
  }
} 
```

--------------------------------------------------------------------------------
/migrations_mcp/handlers/utils.py:
--------------------------------------------------------------------------------

```python
"""Utility functions for migration validation and checks."""
import os
import re
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple

from django.apps import apps
from django.db.migrations.loader import MigrationLoader


def get_migration_files(app_label: str) -> List[str]:
    """Get all migration files for an app."""
    app_config = apps.get_app_config(app_label)
    migrations_dir = Path(app_config.path) / 'migrations'
    
    if not migrations_dir.exists():
        return []
    
    return [
        f.name for f in migrations_dir.iterdir()
        if f.is_file() and f.name.endswith('.py')
        and not f.name.startswith('__')
    ]

def parse_migration_number(filename: str) -> Optional[int]:
    """Extract migration number from filename."""
    match = re.match(r'^(\d{4})_.*\.py$', filename)
    return int(match.group(1)) if match else None

def check_sequential_order(app_label: str) -> Tuple[bool, List[str]]:
    """Check if migrations are in sequential order."""
    files = get_migration_files(app_label)
    numbers = [parse_migration_number(f) for f in files]
    numbers = [n for n in numbers if n is not None]
    
    if not numbers:
        return True, []
    
    expected = list(range(min(numbers), max(numbers) + 1))
    missing = set(expected) - set(numbers)
    
    if missing:
        return False, [
            f"Missing migration number(s): {', '.join(map(str, missing))}"
        ]
    return True, []

def detect_conflicts(app_label: str) -> List[str]:
    """Detect migration conflicts."""
    loader = MigrationLoader(None)
    conflicts = []
    
    # Check for conflicts in the migration graph
    if loader.graph.conflicts:
        for app, nodes in loader.graph.conflicts.items():
            if app == app_label:
                conflicts.extend([
                    f"Conflict in {app}: migrations {', '.join(nodes)}"
                ])
    
    return conflicts

def validate_dependencies(app_label: str) -> List[str]:
    """Validate migration dependencies."""
    loader = MigrationLoader(None)
    errors = []
    
    for migration in loader.disk_migrations.values():
        if migration.app_label == app_label:
            for dep_app, dep_name in migration.dependencies:
                # Check if dependency exists
                if (dep_app, dep_name) not in loader.disk_migrations:
                    errors.append(
                        f"Missing dependency: {dep_app}.{dep_name} "
                        f"required by {app_label}.{migration.name}"
                    )
    
    return errors

def get_migration_plan(app_label: Optional[str] = None) -> List[Tuple[str, bool]]:
    """Get the migration plan showing what needs to be applied."""
    from django.db import connections
    from django.db.migrations.executor import MigrationExecutor
    
    executor = MigrationExecutor(connections['default'])
    plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
    
    if app_label:
        plan = [
            (migration, backwards)
            for migration, backwards in plan
            if migration.app_label == app_label
        ]
    
    return plan

def check_migration_safety(
    app_label: str,
    migration_name: str
) -> Tuple[bool, List[str]]:
    """Check if a migration is safe to apply."""
    warnings = []
    is_safe = True
    
    # Load the migration
    loader = MigrationLoader(None)
    try:
        migration = loader.get_migration_by_prefix(app_label, migration_name)
    except KeyError:
        return False, ["Migration not found"]
    
    # Check for dangerous operations
    for operation in migration.operations:
        op_name = operation.__class__.__name__
        
        if op_name == 'DeleteModel':
            warnings.append(f"Warning: Migration deletes model {operation.name}")
            is_safe = False
        
        elif op_name == 'RemoveField':
            warnings.append(
                f"Warning: Migration removes field {operation.model_name}."
                f"{operation.name}"
            )
            is_safe = False
        
        elif op_name == 'AlterField':
            warnings.append(
                f"Warning: Migration alters field {operation.model_name}."
                f"{operation.name}"
            )
    
    return is_safe, warnings 
```

--------------------------------------------------------------------------------
/migrations_mcp/tests/test_handlers.py:
--------------------------------------------------------------------------------

```python
"""Tests for the Django Migrations MCP service."""
import os
import pytest
from pathlib import Path
from typing import List, Tuple
from unittest.mock import AsyncMock, MagicMock, patch

from django.apps import apps
from django.core.management import call_command
from django.db.migrations.loader import MigrationLoader

from migrations_mcp.service import DjangoMigrationsMCP
from migrations_mcp.handlers.utils import (
    check_sequential_order,
    detect_conflicts,
    validate_dependencies,
    check_migration_safety
)

@pytest.fixture
def service():
    """Create a DjangoMigrationsMCP service instance."""
    return DjangoMigrationsMCP()

@pytest.fixture
def mock_app_config():
    """Mock Django app configuration."""
    mock = MagicMock()
    mock.path = str(Path(__file__).parent / 'test_migrations')
    return mock

@pytest.fixture
def mock_migration_loader():
    """Mock Django migration loader."""
    mock = MagicMock()
    mock.disk_migrations = {}
    mock.graph.conflicts = {}
    return mock

@pytest.mark.asyncio
async def test_show_migrations(service):
    """Test show_migrations handler."""
    with patch('django.core.management.call_command') as mock_call:
        mock_call.return_value = None
        result = await service.show_migrations()
        assert isinstance(result, list)
        mock_call.assert_called_once_with(
            'showmigrations',
            list=True,
            _callback=pytest.ANY
        )

@pytest.mark.asyncio
async def test_make_migrations(service):
    """Test make_migrations handler."""
    with patch('django.core.management.call_command') as mock_call:
        mock_call.return_value = "Created migration"
        result = await service.make_migrations(
            app_labels=['testapp'],
            dry_run=True
        )
        assert result.success
        assert "successfully" in result.message
        mock_call.assert_called_once_with(
            'makemigrations',
            'testapp',
            dry_run=True,
            verbosity=2
        )

@pytest.mark.asyncio
async def test_migrate(service):
    """Test migrate handler."""
    with patch('django.core.management.call_command') as mock_call:
        mock_call.return_value = "Applied migration"
        result = await service.migrate(
            app_label='testapp',
            migration_name='0001',
            fake=True
        )
        assert result.success
        assert "successfully" in result.message
        mock_call.assert_called_once_with(
            'migrate',
            'testapp',
            '0001',
            fake=True,
            plan=False,
            verbosity=2
        )

def test_check_sequential_order(mock_app_config):
    """Test migration sequential order checking."""
    with patch('django.apps.apps.get_app_config', return_value=mock_app_config):
        # Create test migration files
        migrations_dir = Path(mock_app_config.path)
        migrations_dir.mkdir(parents=True, exist_ok=True)
        
        # Create test migration files
        migrations = ['0001_initial.py', '0002_update.py', '0004_change.py']
        for migration in migrations:
            (migrations_dir / migration).touch()
        
        is_sequential, errors = check_sequential_order('testapp')
        assert not is_sequential
        assert any('Missing migration number(s): 3' in error for error in errors)
        
        # Cleanup
        for migration in migrations:
            (migrations_dir / migration).unlink()
        migrations_dir.rmdir()

def test_detect_conflicts(mock_migration_loader):
    """Test migration conflict detection."""
    with patch('migrations_mcp.handlers.utils.MigrationLoader',
              return_value=mock_migration_loader):
        mock_migration_loader.graph.conflicts = {
            'testapp': ['0001_initial', '0001_other']
        }
        
        conflicts = detect_conflicts('testapp')
        assert len(conflicts) == 1
        assert 'Conflict in testapp' in conflicts[0]

def test_validate_dependencies(mock_migration_loader):
    """Test migration dependency validation."""
    with patch('migrations_mcp.handlers.utils.MigrationLoader',
              return_value=mock_migration_loader):
        # Mock a migration with missing dependency
        migration = MagicMock()
        migration.app_label = 'testapp'
        migration.name = '0001_initial'
        migration.dependencies = [('other_app', '0001_initial')]
        
        mock_migration_loader.disk_migrations = {
            ('testapp', '0001_initial'): migration
        }
        
        errors = validate_dependencies('testapp')
        assert len(errors) == 1
        assert 'Missing dependency' in errors[0]

def test_check_migration_safety():
    """Test migration safety checking."""
    with patch('migrations_mcp.handlers.utils.MigrationLoader') as mock_loader:
        # Mock a migration with unsafe operations
        migration = MagicMock()
        delete_op = MagicMock()
        delete_op.__class__.__name__ = 'DeleteModel'
        delete_op.name = 'TestModel'
        migration.operations = [delete_op]
        
        mock_loader_instance = MagicMock()
        mock_loader_instance.get_migration_by_prefix.return_value = migration
        mock_loader.return_value = mock_loader_instance
        
        is_safe, warnings = check_migration_safety('testapp', '0001')
        assert not is_safe
        assert len(warnings) == 1
        assert 'deletes model' in warnings[0]

if __name__ == '__main__':
    pytest.main([__file__]) 
```

--------------------------------------------------------------------------------
/migationsenv/bin/Activate.ps1:
--------------------------------------------------------------------------------

```
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.

.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.

.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.

.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').

.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.

.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.

.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.

.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.

.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:

PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

For more information on Execution Policies: 
https://go.microsoft.com/fwlink/?LinkID=135170

#>
Param(
    [Parameter(Mandatory = $false)]
    [String]
    $VenvDir,
    [Parameter(Mandatory = $false)]
    [String]
    $Prompt
)

<# Function declarations --------------------------------------------------- #>

<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.

.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.

#>
function global:deactivate ([switch]$NonDestructive) {
    # Revert to original values

    # The prior prompt:
    if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
        Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
        Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
    }

    # The prior PYTHONHOME:
    if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
        Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
        Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
    }

    # The prior PATH:
    if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
        Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
        Remove-Item -Path Env:_OLD_VIRTUAL_PATH
    }

    # Just remove the VIRTUAL_ENV altogether:
    if (Test-Path -Path Env:VIRTUAL_ENV) {
        Remove-Item -Path env:VIRTUAL_ENV
    }

    # Just remove VIRTUAL_ENV_PROMPT altogether.
    if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
        Remove-Item -Path env:VIRTUAL_ENV_PROMPT
    }

    # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
    if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
        Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
    }

    # Leave deactivate function in the global namespace if requested:
    if (-not $NonDestructive) {
        Remove-Item -Path function:deactivate
    }
}

<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.

For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.

If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.

.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
    [String]
    $ConfigDir
) {
    Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"

    # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
    $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue

    # An empty map will be returned if no config file is found.
    $pyvenvConfig = @{ }

    if ($pyvenvConfigPath) {

        Write-Verbose "File exists, parse `key = value` lines"
        $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath

        $pyvenvConfigContent | ForEach-Object {
            $keyval = $PSItem -split "\s*=\s*", 2
            if ($keyval[0] -and $keyval[1]) {
                $val = $keyval[1]

                # Remove extraneous quotations around a string value.
                if ("'""".Contains($val.Substring(0, 1))) {
                    $val = $val.Substring(1, $val.Length - 2)
                }

                $pyvenvConfig[$keyval[0]] = $val
                Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
            }
        }
    }
    return $pyvenvConfig
}


<# Begin Activate script --------------------------------------------------- #>

# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath

Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"

# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
    Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
    Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
    $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
    Write-Verbose "VenvDir=$VenvDir"
}

# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir

# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
    Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
    Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
    if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
        Write-Verbose "  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
        $Prompt = $pyvenvCfg['prompt'];
    }
    else {
        Write-Verbose "  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
        Write-Verbose "  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
        $Prompt = Split-Path -Path $venvDir -Leaf
    }
}

Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"

# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive

# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir

if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {

    Write-Verbose "Setting prompt to '$Prompt'"

    # Set the prompt to include the env name
    # Make sure _OLD_VIRTUAL_PROMPT is global
    function global:_OLD_VIRTUAL_PROMPT { "" }
    Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
    New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt

    function global:prompt {
        Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
        _OLD_VIRTUAL_PROMPT
    }
    $env:VIRTUAL_ENV_PROMPT = $Prompt
}

# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
    Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
    Remove-Item -Path Env:PYTHONHOME
}

# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

```