diff --git a/.env.example b/.env.example index cfe23bafe..00b6f06a5 100644 --- a/.env.example +++ b/.env.example @@ -15,13 +15,20 @@ DOCS_ALLOW_BASIC_AUTH=false # Database Configuration # Optimized for v0.7.0 multitenancy with enhanced connection pooling and timeouts -# macOS note: -# If you see "sqlite3.OperationalError: disk I/O error" on macOS when running + +# SQLite (default) - good for development and small deployments +# macOS note: If you see "sqlite3.OperationalError: disk I/O error" on macOS when running # `make serve`, move the DB to a safe APFS path (avoid iCloud/Dropbox/OneDrive/Google Drive, # network shares, or external exFAT) and use an absolute path, for example: # DATABASE_URL=sqlite:////Users/$USER/Library/Application Support/mcpgateway/mcp.db DATABASE_URL=sqlite:///./mcp.db + +# PostgreSQL - recommended for production deployments # DATABASE_URL=postgresql://postgres:mysecretpassword@localhost:5432/mcp + +# MariaDB/MySQL - fully supported for production +# For container deployment: mysql+pymysql://mysql:changeme@mariadb:3306/mcp +# For localhost: mysql+pymysql://mysql:changeme@localhost:3306/mcp # DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp # Database Connection Pool Configuration (optimized for v0.7.0 multitenancy) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79690c526..f0b776df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) DB_POOL_TIMEOUT=30 # Seconds to wait for connection before timeout (default: 30) DB_POOL_RECYCLE=3600 # Seconds before recreating connection (default: 3600) ``` +* **Complete MariaDB & MySQL Database Support** (#925) - Full production support for MariaDB and MySQL backends: + ```bash + # MariaDB (recommended MySQL-compatible option): + DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp + + # Docker deployment with MariaDB 12.0.2-ubi10: + DATABASE_URL=mysql+pymysql://mysql:changeme@mariadb:3306/mcp + ``` + - **36+ database tables** fully compatible with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** resolved for MySQL compatibility + - **Container support**: MariaDB and MySQL drivers included in all container images + - **Complete feature parity** with SQLite and PostgreSQL backends + - **Production ready**: Supports all MCP Gateway features including federation, caching, and A2A agents + * **Enhanced JWT Configuration** - Audience, issuer claims, and improved token validation: ```bash # New JWT configuration options: diff --git a/Containerfile b/Containerfile index d87a6aecb..0bb989f3f 100644 --- a/Containerfile +++ b/Containerfile @@ -24,7 +24,7 @@ COPY . /app # Including observability packages for OpenTelemetry support RUN python3 -m venv /app/.venv && \ /app/.venv/bin/python3 -m pip install --upgrade pip setuptools pdm uv && \ - /app/.venv/bin/python3 -m uv pip install ".[redis,postgres,alembic,observability]" + /app/.venv/bin/python3 -m uv pip install ".[redis,postgres,mysql,alembic,observability]" # update the user permissions RUN chown -R 1001:0 /app && \ diff --git a/Containerfile.lite b/Containerfile.lite index 96e426db5..a0d5f14a2 100644 --- a/Containerfile.lite +++ b/Containerfile.lite @@ -68,7 +68,7 @@ COPY pyproject.toml /app/ RUN set -euo pipefail \ && python3 -m venv /app/.venv \ && /app/.venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel pdm uv \ - && /app/.venv/bin/uv pip install ".[redis,postgres,observability]" \ + && /app/.venv/bin/uv pip install ".[redis,postgres,mysql,observability]" \ && /app/.venv/bin/pip uninstall --yes uv pip setuptools wheel pdm \ && rm -rf /root/.cache /var/cache/dnf \ && find /app/.venv -name "*.dist-info" -type d \ diff --git a/docker-compose.yml b/docker-compose.yml index 93aa2f16e..127eca91d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,8 +24,8 @@ services: # MCP Gateway - the main API server for the MCP stack # ────────────────────────────────────────────────────────────────────── gateway: - #image: ghcr.io/ibm/mcp-context-forge:0.6.0 # Use the release MCP Context Forge image - image: ${IMAGE_LOCAL:-mcpgateway/mcpgateway:latest} # Use the local latest image. Run `make docker-prod` to build it. + image: ghcr.io/ibm/mcp-context-forge:0.6.0 # Use the release MCP Context Forge image + #image: ${IMAGE_LOCAL:-mcpgateway/mcpgateway:latest} # Use the local latest image. Run `make docker-prod` to build it. build: context: . dockerfile: Containerfile # Same one the Makefile builds @@ -41,7 +41,7 @@ services: - HOST=0.0.0.0 - PORT=4444 - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD:-mysecretpassword}@postgres:5432/mcp - # - DATABASE_URL=mysql+pymysql://mysql:${MYSQL_PASSWORD:-changeme}@mysql:3306/mcp + # - DATABASE_URL=mysql+pymysql://mysql:${MYSQL_PASSWORD:-changeme}@mariadb:3306/mcp # - DATABASE_URL=mysql+pymysql://admin:${MARIADB_PASSWORD:-changeme}@mariadb:3306/mcp # - DATABASE_URL=mongodb://admin:${MONGO_PASSWORD:-changeme}@mongodb:27017/mcp - CACHE_TYPE=redis # backend for caching (memory, redis, database, or none) @@ -120,15 +120,23 @@ services: # volumes: [mariadbdata:/var/lib/mysql] # networks: [mcpnet] - # mysql: - # image: mysql:8 + # mariadb: + # image: registry.redhat.io/rhel9/mariadb-106:12.0.2-ubi10 # environment: # - MYSQL_ROOT_PASSWORD=mysecretpassword # - MYSQL_DATABASE=mcp # - MYSQL_USER=mysql # - MYSQL_PASSWORD=changeme - # volumes: [mysqldata:/var/lib/mysql] + # volumes: ["mariadbdata:/var/lib/mysql"] # networks: [mcpnet] + # ports: + # - "3306:3306" + # healthcheck: + # test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pmysecretpassword"] + # interval: 30s + # timeout: 10s + # retries: 5 + # start_period: 30s # mongodb: # image: mongo:7 diff --git a/docs/docs/deployment/compose.md b/docs/docs/deployment/compose.md index 652646faa..4476f3b95 100644 --- a/docs/docs/deployment/compose.md +++ b/docs/docs/deployment/compose.md @@ -98,15 +98,23 @@ curl http://localhost:4444/health # {"status":"ok"} Uncomment one service block in `docker-compose.yml` and align `DATABASE_URL`: -| Service block | Connection string | -| --------------------- | --------------------------------------------- | -| `postgres:` (default) | `postgresql://postgres:...@postgres:5432/mcp` | -| `mariadb:` | `mysql+pymysql://admin:...@mariadb:3306/mcp` | -| `mysql:` | `mysql+pymysql://mysql:...@mysql:3306/mcp` | -| `mongodb:` | `mongodb://admin:...@mongodb:27017/mcp` | +| Service block | Connection string | Notes | +| --------------------- | --------------------------------------------- | ------------------------------ | +| `postgres:` (default) | `postgresql://postgres:...@postgres:5432/mcp` | Recommended for production | +| `mariadb:` | `mysql+pymysql://mysql:...@mariadb:3306/mcp` | **Fully supported** - MariaDB 12.0+ | +| `mysql:` | `mysql+pymysql://admin:...@mysql:3306/mcp` | Alternative MySQL variant | +| `mongodb:` | `mongodb://admin:...@mongodb:27017/mcp` | NoSQL option | Named volumes (`pgdata`, `mariadbdata`, `mysqldata`, `mongodata`) isolate persistent data. +!!! info "MariaDB & MySQL Full Support" + MariaDB and MySQL are **fully supported** alongside SQLite and PostgreSQL: + + - **36+ database tables** work perfectly with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** have been resolved for MariaDB/MySQL compatibility + - Simply uncomment the `mariadb:` service block in `docker-compose.yml` + - Use connection string: `mysql+pymysql://mysql:changeme@mariadb:3306/mcp` + --- ## 🔄 Lifecycle cheatsheet diff --git a/docs/docs/deployment/kubernetes.md b/docs/docs/deployment/kubernetes.md index 67eae5ed1..b028d34fe 100644 --- a/docs/docs/deployment/kubernetes.md +++ b/docs/docs/deployment/kubernetes.md @@ -96,14 +96,151 @@ spec: You can load your `.env` as a ConfigMap: -```bash -kubectl create configmap mcpgateway-env --from-env-file=.env +=== "With SQLite (Default)" + ```bash + # Create .env file + cat > .env << EOF + HOST=0.0.0.0 + PORT=4444 + DATABASE_URL=sqlite:///./mcp.db + JWT_SECRET_KEY=your-secret-key + BASIC_AUTH_USER=admin + BASIC_AUTH_PASSWORD=changeme + MCPGATEWAY_UI_ENABLED=true + MCPGATEWAY_ADMIN_API_ENABLED=true + EOF + + kubectl create configmap mcpgateway-env --from-env-file=.env + ``` + +=== "With MariaDB" + ```bash + # Create .env file + cat > .env << EOF + HOST=0.0.0.0 + PORT=4444 + DATABASE_URL=mysql+pymysql://mysql:changeme@mariadb-service:3306/mcp + JWT_SECRET_KEY=your-secret-key + BASIC_AUTH_USER=admin + BASIC_AUTH_PASSWORD=changeme + MCPGATEWAY_UI_ENABLED=true + MCPGATEWAY_ADMIN_API_ENABLED=true + EOF + + kubectl create configmap mcpgateway-env --from-env-file=.env + ``` + +=== "With MySQL" + ```bash + # Create .env file + cat > .env << EOF + HOST=0.0.0.0 + PORT=4444 + DATABASE_URL=mysql+pymysql://mysql:changeme@mysql-service:3306/mcp + JWT_SECRET_KEY=your-secret-key + BASIC_AUTH_USER=admin + BASIC_AUTH_PASSWORD=changeme + MCPGATEWAY_UI_ENABLED=true + MCPGATEWAY_ADMIN_API_ENABLED=true + EOF + + kubectl create configmap mcpgateway-env --from-env-file=.env + ``` + +=== "With PostgreSQL" + ```bash + # Create .env file + cat > .env << EOF + HOST=0.0.0.0 + PORT=4444 + DATABASE_URL=postgresql://postgres:changeme@postgres-service:5432/mcp + JWT_SECRET_KEY=your-secret-key + BASIC_AUTH_USER=admin + BASIC_AUTH_PASSWORD=changeme + MCPGATEWAY_UI_ENABLED=true + MCPGATEWAY_ADMIN_API_ENABLED=true + EOF + + kubectl create configmap mcpgateway-env --from-env-file=.env ``` > Make sure it includes `JWT_SECRET_KEY`, `AUTH_REQUIRED`, etc. --- +## 🗄 Database Deployment Examples + +### MySQL Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql:8 + env: + - name: MYSQL_ROOT_PASSWORD + value: mysecretpassword + - name: MYSQL_DATABASE + value: mcp + - name: MYSQL_USER + value: mysql + - name: MYSQL_PASSWORD + value: changeme + ports: + - containerPort: 3306 + volumeMounts: + - name: mysql-storage + mountPath: /var/lib/mysql + volumes: + - name: mysql-storage + persistentVolumeClaim: + claimName: mysql-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: mysql-service +spec: + selector: + app: mysql + ports: + - port: 3306 + targetPort: 3306 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +!!! info "MariaDB & MySQL Kubernetes Support" + MariaDB and MySQL are **fully supported** in Kubernetes deployments: + + - **36+ database tables** work perfectly with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** resolved for MariaDB/MySQL compatibility + - Use connection string: `mysql+pymysql://mysql:changeme@mariadb-service:3306/mcp` + +--- + ## 💡 OpenShift Considerations * Use `Route` instead of Ingress diff --git a/docs/docs/deployment/local.md b/docs/docs/deployment/local.md index 33442faf6..2ef664298 100644 --- a/docs/docs/deployment/local.md +++ b/docs/docs/deployment/local.md @@ -40,6 +40,69 @@ make dev # hot-reload (Uvicorn) on :8000 --- +## 🗄 Database Configuration + +By default, MCP Gateway uses SQLite for simplicity. You can configure alternative databases via the `DATABASE_URL` environment variable: + +=== "SQLite (Default)" + ```bash + # .env file + DATABASE_URL=sqlite:///./mcp.db + ``` + +=== "MariaDB" + ```bash + # .env file + DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp + ``` + + !!! info "MariaDB Setup" + Install and configure MariaDB server: + ```bash + # Ubuntu/Debian + sudo apt update && sudo apt install mariadb-server + + # Create database and user + sudo mariadb -e "CREATE DATABASE mcp;" + sudo mariadb -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mariadb -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mariadb -e "FLUSH PRIVILEGES;" + ``` + +=== "MySQL" + ```bash + # .env file + DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp + ``` + + !!! info "MySQL Setup" + Install and configure MySQL server: + ```bash + # Ubuntu/Debian + sudo apt update && sudo apt install mysql-server + + # Create database and user + sudo mysql -e "CREATE DATABASE mcp;" + sudo mysql -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mysql -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mysql -e "FLUSH PRIVILEGES;" + ``` + +=== "PostgreSQL" + ```bash + # .env file + DATABASE_URL=postgresql://postgres:changeme@localhost:5432/mcp + ``` + +!!! tip "MariaDB & MySQL Full Compatibility" + MariaDB and MySQL are **fully supported** with: + + - **36+ database tables** working perfectly with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** resolved for MariaDB/MySQL compatibility + - Complete feature parity with SQLite and PostgreSQL + +--- + ## 🧪 Health Test ```bash diff --git a/docs/docs/manage/.pages b/docs/docs/manage/.pages index 7a0686abe..45683c6b1 100644 --- a/docs/docs/manage/.pages +++ b/docs/docs/manage/.pages @@ -1,5 +1,6 @@ nav: - index.md + - configuration.md - backup.md - bulk-import.md - metadata-tracking.md diff --git a/docs/docs/manage/configuration.md b/docs/docs/manage/configuration.md new file mode 100644 index 000000000..bc91ab869 --- /dev/null +++ b/docs/docs/manage/configuration.md @@ -0,0 +1,466 @@ +# Configuration Reference + +This guide provides comprehensive configuration options for MCP Gateway, including database setup, environment variables, and deployment-specific settings. + +--- + +## 🗄 Database Configuration + +MCP Gateway supports multiple database backends with full feature parity across all supported systems. + +### Supported Databases + +| Database | Support Level | Connection String Example | Notes | +|-------------|---------------|--------------------------------------------------------------|--------------------------------| +| SQLite | ✅ Full | `sqlite:///./mcp.db` | Default, file-based | +| PostgreSQL | ✅ Full | `postgresql://postgres:changeme@localhost:5432/mcp` | Recommended for production | +| MariaDB | ✅ Full | `mysql+pymysql://mysql:changeme@localhost:3306/mcp` | **36+ tables**, MariaDB 12.0+ | +| MySQL | ✅ Full | `mysql+pymysql://admin:changeme@localhost:3306/mcp` | Alternative MySQL variant | +| MongoDB | ✅ Full | `mongodb://admin:changeme@localhost:27017/mcp` | NoSQL document store | + +### MariaDB/MySQL Setup Details + +!!! success "MariaDB & MySQL Full Support" + MariaDB and MySQL are **fully supported** alongside SQLite and PostgreSQL: + + - **36+ database tables** work perfectly with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** have been resolved for MariaDB/MySQL compatibility + - Complete feature parity with SQLite and PostgreSQL + - Supports all MCP Gateway features including federation, caching, and A2A agents + +#### Connection String Format + +```bash +DATABASE_URL=mysql+pymysql://[username]:[password]@[host]:[port]/[database] +``` + +#### Local MariaDB/MySQL Installation + +=== "Ubuntu/Debian (MariaDB)" + ```bash + # Install MariaDB server + sudo apt update && sudo apt install mariadb-server + + # Secure installation (optional) + sudo mariadb-secure-installation + + # Create database and user + sudo mariadb -e "CREATE DATABASE mcp;" + sudo mariadb -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mariadb -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mariadb -e "FLUSH PRIVILEGES;" + ``` + +=== "Ubuntu/Debian (MySQL)" + ```bash + # Install MySQL server + sudo apt update && sudo apt install mysql-server + + # Secure installation (optional) + sudo mysql_secure_installation + + # Create database and user + sudo mysql -e "CREATE DATABASE mcp;" + sudo mysql -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mysql -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mysql -e "FLUSH PRIVILEGES;" + ``` + +=== "CentOS/RHEL/Fedora (MariaDB)" + ```bash + # Install MariaDB server + sudo dnf install mariadb-server + sudo systemctl start mariadb + sudo systemctl enable mariadb + + # Create database and user + sudo mariadb -e "CREATE DATABASE mcp;" + sudo mariadb -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mariadb -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mariadb -e "FLUSH PRIVILEGES;" + ``` + +=== "CentOS/RHEL/Fedora (MySQL)" + ```bash + # Install MySQL server + sudo dnf install mysql-server # or: sudo yum install mysql-server + sudo systemctl start mysqld + sudo systemctl enable mysqld + + # Create database and user + sudo mysql -e "CREATE DATABASE mcp;" + sudo mysql -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + sudo mysql -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + sudo mysql -e "FLUSH PRIVILEGES;" + ``` + +=== "macOS (Homebrew - MariaDB)" + ```bash + # Install MariaDB + brew install mariadb + brew services start mariadb + + # Create database and user + mariadb -u root -e "CREATE DATABASE mcp;" + mariadb -u root -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + mariadb -u root -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + mariadb -u root -e "FLUSH PRIVILEGES;" + ``` + +=== "macOS (Homebrew - MySQL)" + ```bash + # Install MySQL + brew install mysql + brew services start mysql + + # Create database and user + mysql -u root -e "CREATE DATABASE mcp;" + mysql -u root -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';" + mysql -u root -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';" + mysql -u root -e "FLUSH PRIVILEGES;" + ``` + +#### Docker MariaDB/MySQL Setup + +```bash +# Start MariaDB container (recommended) +docker run -d --name mariadb-mcp \ + -e MYSQL_ROOT_PASSWORD=mysecretpassword \ + -e MYSQL_DATABASE=mcp \ + -e MYSQL_USER=mysql \ + -e MYSQL_PASSWORD=changeme \ + -p 3306:3306 \ + registry.redhat.io/rhel9/mariadb-106:12.0.2-ubi10 + +# Or start MySQL container +docker run -d --name mysql-mcp \ + -e MYSQL_ROOT_PASSWORD=mysecretpassword \ + -e MYSQL_DATABASE=mcp \ + -e MYSQL_USER=mysql \ + -e MYSQL_PASSWORD=changeme \ + -p 3306:3306 \ + mysql:8 + +# Connection string for MCP Gateway (same for both) +DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp +``` + +--- + +## 🔧 Core Environment Variables + +### Database Settings + +```bash +# Database connection (choose one) +DATABASE_URL=sqlite:///./mcp.db # SQLite (default) +DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp # MySQL +DATABASE_URL=postgresql://postgres:changeme@localhost:5432/mcp # PostgreSQL +DATABASE_URL=mongodb://admin:changeme@localhost:27017/mcp # MongoDB + +# Connection pool settings (optional) +DATABASE_POOL_SIZE=10 +DATABASE_MAX_OVERFLOW=20 +DATABASE_POOL_TIMEOUT=30 +``` + +### Server Configuration + +```bash +# Network binding +HOST=0.0.0.0 +PORT=4444 + +# SSL/TLS (optional) +SSL=false +CERT_FILE=/app/certs/cert.pem +KEY_FILE=/app/certs/key.pem +``` + +### Authentication & Security + +```bash +# JWT Configuration +JWT_SECRET_KEY=your-secret-key-here +JWT_AUDIENCE=mcpgateway-api +JWT_ISSUER=mcpgateway +REQUIRE_TOKEN_EXPIRATION=true + +# Basic Auth (Admin UI) +BASIC_AUTH_USER=admin +BASIC_AUTH_PASSWORD=changeme + +# Email-based Auth +EMAIL_AUTH_ENABLED=true +PLATFORM_ADMIN_EMAIL=admin@example.com +PLATFORM_ADMIN_PASSWORD=changeme + +# Security Features +SECURITY_HEADERS_ENABLED=true +CORS_ALLOW_CREDENTIALS=true +``` + +### Feature Flags + +```bash +# Core Features +MCPGATEWAY_UI_ENABLED=true +MCPGATEWAY_ADMIN_API_ENABLED=true +MCPGATEWAY_BULK_IMPORT_ENABLED=true +MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200 + +# A2A (Agent-to-Agent) Features +MCPGATEWAY_A2A_ENABLED=true +MCPGATEWAY_A2A_MAX_AGENTS=100 +MCPGATEWAY_A2A_DEFAULT_TIMEOUT=30 +MCPGATEWAY_A2A_METRICS_ENABLED=true + +# Federation & Discovery +MCPGATEWAY_ENABLE_FEDERATION=true +MCPGATEWAY_ENABLE_MDNS_DISCOVERY=true +``` + +### Caching Configuration + +```bash +# Cache Backend +CACHE_TYPE=redis # Options: memory, redis, database, none +REDIS_URL=redis://localhost:6379/0 + +# Cache TTL (seconds) +CACHE_DEFAULT_TTL=300 +CACHE_TOOL_TTL=600 +CACHE_RESOURCE_TTL=180 +``` + +### Logging Settings + +```bash +# Log Level +LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL + +# Log Destinations +LOG_TO_FILE=false +LOG_ROTATION_ENABLED=false +LOG_FILE=mcpgateway.log +LOG_FOLDER=logs + +# Structured Logging +LOG_FORMAT=json # json, plain +LOG_INCLUDE_TIMESTAMPS=true +``` + +### Development & Debug + +```bash +# Development Mode +ENVIRONMENT=development # development, staging, production +DEV_MODE=true +RELOAD=true +DEBUG=true + +# Metrics & Observability +METRICS_ENABLED=true +HEALTH_CHECK_ENABLED=true +``` + +--- + +## 🐳 Container Configuration + +### Docker Environment File + +Create a `.env` file for Docker deployments: + +```bash +# .env file for Docker +HOST=0.0.0.0 +PORT=4444 +DATABASE_URL=mysql+pymysql://mysql:changeme@mysql:3306/mcp +REDIS_URL=redis://redis:6379/0 +JWT_SECRET_KEY=my-secret-key +BASIC_AUTH_USER=admin +BASIC_AUTH_PASSWORD=changeme +MCPGATEWAY_UI_ENABLED=true +MCPGATEWAY_ADMIN_API_ENABLED=true +``` + +### Docker Compose with MySQL + +```yaml +version: "3.9" + +services: + gateway: + image: ghcr.io/ibm/mcp-context-forge:latest + ports: + - "4444:4444" + environment: + - DATABASE_URL=mysql+pymysql://mysql:changeme@mysql:3306/mcp + - REDIS_URL=redis://redis:6379/0 + - JWT_SECRET_KEY=my-secret-key + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_started + + mysql: + image: mysql:8 + environment: + - MYSQL_ROOT_PASSWORD=mysecretpassword + - MYSQL_DATABASE=mcp + - MYSQL_USER=mysql + - MYSQL_PASSWORD=changeme + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 30s + timeout: 10s + retries: 5 + + redis: + image: redis:7 + volumes: + - redis_data:/data + +volumes: + mysql_data: + redis_data: +``` + +--- + +## ☸️ Kubernetes Configuration + +### ConfigMap Example + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: mcpgateway-config +data: + DATABASE_URL: "mysql+pymysql://mysql:changeme@mysql-service:3306/mcp" + REDIS_URL: "redis://redis-service:6379/0" + JWT_SECRET_KEY: "your-secret-key" + BASIC_AUTH_USER: "admin" + BASIC_AUTH_PASSWORD: "changeme" + MCPGATEWAY_UI_ENABLED: "true" + MCPGATEWAY_ADMIN_API_ENABLED: "true" + LOG_LEVEL: "INFO" +``` + +### MySQL Service Example + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql:8 + env: + - name: MYSQL_ROOT_PASSWORD + value: "mysecretpassword" + - name: MYSQL_DATABASE + value: "mcp" + - name: MYSQL_USER + value: "mysql" + - name: MYSQL_PASSWORD + value: "changeme" + volumeMounts: + - name: mysql-storage + mountPath: /var/lib/mysql + volumes: + - name: mysql-storage + persistentVolumeClaim: + claimName: mysql-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: mysql-service +spec: + selector: + app: mysql + ports: + - port: 3306 + targetPort: 3306 +``` + +--- + +## 🔧 Advanced Configuration + +### Performance Tuning + +```bash +# Database Connection Pool +DATABASE_POOL_SIZE=20 +DATABASE_MAX_OVERFLOW=30 +DATABASE_POOL_TIMEOUT=60 +DATABASE_POOL_RECYCLE=3600 + +# HTTP Settings +HTTP_WORKERS=4 +HTTP_KEEPALIVE=2 +HTTP_TIMEOUT=30 + +# Tool Execution +TOOL_EXECUTION_TIMEOUT=300 +MAX_CONCURRENT_TOOLS=10 +``` + +### Security Hardening + +```bash +# Enable all security features +SECURITY_HEADERS_ENABLED=true +CORS_ALLOW_CREDENTIALS=false +REQUIRE_TOKEN_EXPIRATION=true +JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15 +JWT_REFRESH_TOKEN_EXPIRE_DAYS=7 + +# Rate limiting +RATE_LIMIT_ENABLED=true +RATE_LIMIT_REQUESTS_PER_MINUTE=60 +RATE_LIMIT_BURST=10 +``` + +### Observability Integration + +```bash +# OpenTelemetry (Phoenix, Jaeger, etc.) +OTEL_EXPORTER_OTLP_ENDPOINT=http://phoenix:4317 +OTEL_SERVICE_NAME=mcp-gateway +OTEL_TRACES_EXPORTER=otlp +OTEL_METRICS_EXPORTER=otlp + +# Prometheus Metrics +METRICS_ENABLED=true +METRICS_PORT=9090 +METRICS_PATH=/metrics +``` + +--- + +## 📚 Related Documentation + +- [Docker Compose Deployment](../deployment/compose.md) +- [Local Development Setup](../deployment/local.md) +- [Kubernetes Deployment](../deployment/kubernetes.md) +- [Backup & Restore](backup.md) +- [Logging Configuration](logging.md) diff --git a/docs/docs/manage/index.md b/docs/docs/manage/index.md index 07e40e7fb..2ca0fd6e0 100644 --- a/docs/docs/manage/index.md +++ b/docs/docs/manage/index.md @@ -10,6 +10,7 @@ Whether you're self-hosting, running in the cloud, or deploying to Kubernetes, t | Page | Description | |------|-------------| +| [Configuration](configuration.md) | **Complete configuration reference** - databases, environment variables, and deployment settings | | [Backups](backup.md) | How to persist and restore your database, configs, and resource state | | [Export & Import](export-import.md) | Complete configuration management with CLI, API, and Admin UI | | [Export/Import Tutorial](export-import-tutorial.md) | Step-by-step tutorial for getting started with export/import | @@ -25,6 +26,14 @@ Whether you're self-hosting, running in the cloud, or deploying to Kubernetes, t Most operational settings (logging level, database pool size, auth mode) are controlled through `.env` or environment variables. +!!! info "MariaDB & MySQL Fully Supported" + MCP Gateway now has **complete MariaDB/MySQL support** alongside SQLite and PostgreSQL: + + - **36+ database tables** work perfectly with MariaDB 12.0+ and MySQL 8.4+ + - All **VARCHAR length issues** resolved for MariaDB/MySQL compatibility + - Connection string: `DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp` + - See [Configuration Reference](configuration.md) for complete setup instructions + Update the file and restart the container or process to apply changes. --- diff --git a/docs/docs/overview/quick_start.md b/docs/docs/overview/quick_start.md index 1ceabb130..ca67f355c 100644 --- a/docs/docs/overview/quick_start.md +++ b/docs/docs/overview/quick_start.md @@ -92,17 +92,61 @@ Pick an install method below, generate an auth token, then walk through a real t 2. **(Optional) persist the DB** - ```bash - mkdir -p $(pwd)/data - docker run -d --name mcpgateway \ - -p 4444:4444 \ - -v $(pwd)/data:/data \ - -e DATABASE_URL=sqlite:////data/mcp.db \ - -e JWT_SECRET_KEY=my-test-key \ - -e BASIC_AUTH_USER=admin \ - -e BASIC_AUTH_PASSWORD=changeme \ - ghcr.io/ibm/mcp-context-forge:0.6.0 - ``` + === "SQLite (Default)" + ```bash + mkdir -p $(pwd)/data + docker run -d --name mcpgateway \ + -p 4444:4444 \ + -v $(pwd)/data:/data \ + -e DATABASE_URL=sqlite:////data/mcp.db \ + -e JWT_SECRET_KEY=my-test-key \ + -e BASIC_AUTH_USER=admin \ + -e BASIC_AUTH_PASSWORD=changeme \ + ghcr.io/ibm/mcp-context-forge:0.6.0 + ``` + + === "MySQL" + ```bash + # Start MySQL container first + docker run -d --name mysql-db \ + -e MYSQL_ROOT_PASSWORD=mysecretpassword \ + -e MYSQL_DATABASE=mcp \ + -e MYSQL_USER=mysql \ + -e MYSQL_PASSWORD=changeme \ + -p 3306:3306 \ + mysql:8 + + # Start MCP Gateway with MySQL connection + docker run -d --name mcpgateway \ + -p 4444:4444 \ + --link mysql-db:mysql \ + -e DATABASE_URL=mysql+pymysql://mysql:changeme@mysql:3306/mcp \ + -e JWT_SECRET_KEY=my-test-key \ + -e BASIC_AUTH_USER=admin \ + -e BASIC_AUTH_PASSWORD=changeme \ + ghcr.io/ibm/mcp-context-forge:0.6.0 + ``` + + === "PostgreSQL" + ```bash + # Start PostgreSQL container first + docker run -d --name postgres-db \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=mysecretpassword \ + -e POSTGRES_DB=mcp \ + -p 5432:5432 \ + postgres:17 + + # Start MCP Gateway with PostgreSQL connection + docker run -d --name mcpgateway \ + -p 4444:4444 \ + --link postgres-db:postgres \ + -e DATABASE_URL=postgresql://postgres:mysecretpassword@postgres:5432/mcp \ + -e JWT_SECRET_KEY=my-test-key \ + -e BASIC_AUTH_USER=admin \ + -e BASIC_AUTH_PASSWORD=changeme \ + ghcr.io/ibm/mcp-context-forge:0.6.0 + ``` 3. **Generate a token inside the container** @@ -156,9 +200,15 @@ Pick an install method below, generate an auth token, then walk through a real t curl -s http://localhost:4444/health | jq ``` - > **Tip :** The sample Compose file has multiple database blocks - > (Postgres, MariaDB, MySQL, MongoDB) and admin tools. Uncomment one and align - > `DATABASE_URL` for your preferred backend. + !!! tip "Database Support" + The sample Compose file includes multiple database options: + + - **PostgreSQL** (default): `postgresql://postgres:password@postgres:5432/mcp` + - **MariaDB**: `mysql+pymysql://mysql:changeme@mariadb:3306/mcp` - fully supported with 36+ tables + - **MySQL**: `mysql+pymysql://admin:changeme@mysql:3306/mcp` + - **MongoDB**: `mongodb://admin:changeme@mongodb:27017/mcp` + + MariaDB 12.0+ and MySQL 8.4+ are fully compatible with all VARCHAR length requirements resolved. --- diff --git a/mcpgateway/alembic/versions/34492f99a0c4_add_comprehensive_metadata_to_all_.py b/mcpgateway/alembic/versions/34492f99a0c4_add_comprehensive_metadata_to_all_.py index 135a06323..486acbdad 100644 --- a/mcpgateway/alembic/versions/34492f99a0c4_add_comprehensive_metadata_to_all_.py +++ b/mcpgateway/alembic/versions/34492f99a0c4_add_comprehensive_metadata_to_all_.py @@ -39,16 +39,16 @@ def upgrade() -> None: # Define metadata columns to add metadata_columns = [ - ("created_by", sa.String(), True), - ("created_from_ip", sa.String(), True), - ("created_via", sa.String(), True), + ("created_by", sa.String(255), True), + ("created_from_ip", sa.String(45), True), + ("created_via", sa.String(100), True), ("created_user_agent", sa.Text(), True), - ("modified_by", sa.String(), True), - ("modified_from_ip", sa.String(), True), - ("modified_via", sa.String(), True), + ("modified_by", sa.String(255), True), + ("modified_from_ip", sa.String(45), True), + ("modified_via", sa.String(100), True), ("modified_user_agent", sa.Text(), True), - ("import_batch_id", sa.String(), True), - ("federation_source", sa.String(), True), + ("import_batch_id", sa.String(36), True), + ("federation_source", sa.String(255), True), ("version", sa.Integer(), False, "1"), # Not nullable, with default ] diff --git a/mcpgateway/alembic/versions/733159a4fa74_add_display_name_to_tools.py b/mcpgateway/alembic/versions/733159a4fa74_add_display_name_to_tools.py index d45d7002a..c91f2af89 100644 --- a/mcpgateway/alembic/versions/733159a4fa74_add_display_name_to_tools.py +++ b/mcpgateway/alembic/versions/733159a4fa74_add_display_name_to_tools.py @@ -57,7 +57,7 @@ def generate_display_name(technical_name: str) -> str: tools_columns = [col["name"] for col in inspector.get_columns("tools")] if "display_name" not in tools_columns: # Add the column first - op.add_column("tools", sa.Column("display_name", sa.String(), nullable=True)) + op.add_column("tools", sa.Column("display_name", sa.String(255), nullable=True)) print("Added display_name column to tools table.") # Populate smart displayName for existing tools that don't have one diff --git a/mcpgateway/alembic/versions/add_a2a_agents_and_metrics.py b/mcpgateway/alembic/versions/add_a2a_agents_and_metrics.py index 9e98ebbd6..2c022b44a 100644 --- a/mcpgateway/alembic/versions/add_a2a_agents_and_metrics.py +++ b/mcpgateway/alembic/versions/add_a2a_agents_and_metrics.py @@ -38,32 +38,32 @@ def upgrade() -> None: op.create_table( "a2a_agents", sa.Column("id", sa.String(36), primary_key=True), - sa.Column("name", sa.String(), nullable=False), - sa.Column("slug", sa.String(), nullable=False), + sa.Column("name", sa.String(255), nullable=False), + sa.Column("slug", sa.String(255), nullable=False), sa.Column("description", sa.Text()), - sa.Column("endpoint_url", sa.String(), nullable=False), - sa.Column("agent_type", sa.String(), nullable=False, server_default="generic"), - sa.Column("protocol_version", sa.String(), nullable=False, server_default="1.0"), - sa.Column("capabilities", sa.JSON(), server_default="{}"), - sa.Column("config", sa.JSON(), server_default="{}"), - sa.Column("auth_type", sa.String()), + sa.Column("endpoint_url", sa.String(767), nullable=False), + sa.Column("agent_type", sa.String(50), nullable=False, server_default="generic"), + sa.Column("protocol_version", sa.String(10), nullable=False, server_default="1.0"), + sa.Column("capabilities", sa.JSON(), nullable=True), + sa.Column("config", sa.JSON(), nullable=True), + sa.Column("auth_type", sa.String(50)), sa.Column("auth_value", sa.Text()), sa.Column("enabled", sa.Boolean(), server_default="1"), sa.Column("reachable", sa.Boolean(), server_default="1"), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), sa.Column("last_interaction", sa.DateTime(timezone=True)), - sa.Column("tags", sa.JSON(), nullable=False, server_default="[]"), - sa.Column("created_by", sa.String()), - sa.Column("created_from_ip", sa.String()), - sa.Column("created_via", sa.String()), + sa.Column("tags", sa.JSON(), nullable=True), + sa.Column("created_by", sa.String(255)), + sa.Column("created_from_ip", sa.String(45)), + sa.Column("created_via", sa.String(100)), sa.Column("created_user_agent", sa.Text()), - sa.Column("modified_by", sa.String()), - sa.Column("modified_from_ip", sa.String()), - sa.Column("modified_via", sa.String()), + sa.Column("modified_by", sa.String(255)), + sa.Column("modified_from_ip", sa.String(45)), + sa.Column("modified_via", sa.String(100)), sa.Column("modified_user_agent", sa.Text()), - sa.Column("import_batch_id", sa.String()), - sa.Column("federation_source", sa.String()), + sa.Column("import_batch_id", sa.String(36)), + sa.Column("federation_source", sa.String(255)), sa.Column("version", sa.Integer(), nullable=False, server_default="1"), sa.UniqueConstraint("name", name="uq_a2a_agents_name"), sa.UniqueConstraint("slug", name="uq_a2a_agents_slug"), @@ -79,15 +79,16 @@ def upgrade() -> None: sa.Column("response_time", sa.Float(), nullable=False), sa.Column("is_success", sa.Boolean(), nullable=False), sa.Column("error_message", sa.Text()), - sa.Column("interaction_type", sa.String(), nullable=False, server_default="invoke"), + sa.Column("interaction_type", sa.String(50), nullable=False, server_default="invoke"), ) - if "server_a2a_association" not in existing_tables: + # Only create association table if both referenced tables exist + if "server_a2a_association" not in existing_tables and "servers" in existing_tables and "a2a_agents" in existing_tables: # Create server_a2a_association table op.create_table( "server_a2a_association", - sa.Column("server_id", sa.String(), sa.ForeignKey("servers.id"), primary_key=True), - sa.Column("a2a_agent_id", sa.String(), sa.ForeignKey("a2a_agents.id"), primary_key=True), + sa.Column("server_id", sa.String(36), sa.ForeignKey("servers.id"), primary_key=True), + sa.Column("a2a_agent_id", sa.String(36), sa.ForeignKey("a2a_agents.id"), primary_key=True), ) # Create indexes for performance (check if they exist first) diff --git a/mcpgateway/alembic/versions/b77ca9d2de7e_uuid_pk_and_slug_refactor.py b/mcpgateway/alembic/versions/b77ca9d2de7e_uuid_pk_and_slug_refactor.py index 83b2db5aa..46f04310f 100644 --- a/mcpgateway/alembic/versions/b77ca9d2de7e_uuid_pk_and_slug_refactor.py +++ b/mcpgateway/alembic/versions/b77ca9d2de7e_uuid_pk_and_slug_refactor.py @@ -108,13 +108,13 @@ def upgrade() -> None: print("Existing installation detected. Starting data and schema migration...") # ── STAGE 1: ADD NEW NULLABLE COLUMNS AS PLACEHOLDERS ───────────────── - op.add_column("gateways", sa.Column("slug", sa.String(), nullable=True)) + op.add_column("gateways", sa.Column("slug", sa.String(255), nullable=True)) op.add_column("gateways", sa.Column("id_new", sa.String(36), nullable=True)) op.add_column("tools", sa.Column("id_new", sa.String(36), nullable=True)) - op.add_column("tools", sa.Column("original_name", sa.String(), nullable=True)) - op.add_column("tools", sa.Column("original_name_slug", sa.String(), nullable=True)) - op.add_column("tools", sa.Column("name_new", sa.String(), nullable=True)) + op.add_column("tools", sa.Column("original_name", sa.String(255), nullable=True)) + op.add_column("tools", sa.Column("original_name_slug", sa.String(255), nullable=True)) + op.add_column("tools", sa.Column("name_new", sa.String(255), nullable=True)) op.add_column("tools", sa.Column("gateway_id_new", sa.String(36), nullable=True)) op.add_column("resources", sa.Column("gateway_id_new", sa.String(36), nullable=True)) @@ -523,7 +523,7 @@ def downgrade() -> None: # Add back old columns batch_op.add_column(sa.Column("id", sa.Integer(), nullable=True)) batch_op.add_column(sa.Column("gateway_id", sa.Integer(), nullable=True)) - batch_op.add_column(sa.Column("name", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("name", sa.String(255), nullable=True)) with op.batch_alter_table("servers") as batch_op: batch_op.drop_constraint("pk_servers", type_="primarykey") diff --git a/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py b/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py index 19e690d37..0e8826a22 100644 --- a/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py +++ b/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py @@ -49,7 +49,7 @@ def upgrade() -> None: # Add custom_name column if it doesn't exist if "custom_name" not in columns: try: - op.add_column("tools", sa.Column("custom_name", sa.String(), nullable=True)) + op.add_column("tools", sa.Column("custom_name", sa.String(255), nullable=True)) # Only try to update if original_name column exists if "original_name" in columns: op.execute("UPDATE tools SET custom_name = original_name") diff --git a/mcpgateway/alembic/versions/cfc3d6aa0fb2_consolidated_multiuser_team_rbac_.py b/mcpgateway/alembic/versions/cfc3d6aa0fb2_consolidated_multiuser_team_rbac_.py index 895df1677..b48fa5f1a 100644 --- a/mcpgateway/alembic/versions/cfc3d6aa0fb2_consolidated_multiuser_team_rbac_.py +++ b/mcpgateway/alembic/versions/cfc3d6aa0fb2_consolidated_multiuser_team_rbac_.py @@ -206,10 +206,10 @@ def safe_create_index(index_name: str, table_name: str, columns: list): sa.Column("token_hash", sa.String(255), nullable=False, comment="Hashed token value"), # Scoping fields - with proper JSON types and defaults sa.Column("server_id", sa.String(36), nullable=True, comment="Limited to specific server (NULL = global)"), - sa.Column("resource_scopes", sa.JSON(), nullable=True, server_default=sa.text("'[]'"), comment="JSON array of resource permissions"), - sa.Column("ip_restrictions", sa.JSON(), nullable=True, server_default=sa.text("'[]'"), comment="JSON array of allowed IP addresses/CIDR"), - sa.Column("time_restrictions", sa.JSON(), nullable=True, server_default=sa.text("'{}'"), comment="JSON object of time-based restrictions"), - sa.Column("usage_limits", sa.JSON(), nullable=True, server_default=sa.text("'{}'"), comment="JSON object of usage limits"), + sa.Column("resource_scopes", sa.JSON(), nullable=True, comment="JSON array of resource permissions"), + sa.Column("ip_restrictions", sa.JSON(), nullable=True, comment="JSON array of allowed IP addresses/CIDR"), + sa.Column("time_restrictions", sa.JSON(), nullable=True, comment="JSON object of time-based restrictions"), + sa.Column("usage_limits", sa.JSON(), nullable=True, comment="JSON object of usage limits"), # Lifecycle fields sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), comment="Token creation timestamp"), sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True, comment="Token expiry timestamp"), @@ -217,7 +217,7 @@ def safe_create_index(index_name: str, table_name: str, columns: list): sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true(), comment="Active status flag"), # Metadata fields sa.Column("description", sa.Text(), nullable=True, comment="Token description"), - sa.Column("tags", sa.JSON(), nullable=True, server_default=sa.text("'[]'"), comment="JSON array of tags"), + sa.Column("tags", sa.JSON(), nullable=True, comment="JSON array of tags"), sa.Column("team_id", sa.String(length=36), nullable=True), # Team scoping # Constraints sa.PrimaryKeyConstraint("id"), @@ -363,10 +363,10 @@ def safe_create_index(index_name: str, table_name: str, columns: list): sa.Column("token_url", sa.String(500), nullable=False), sa.Column("userinfo_url", sa.String(500), nullable=False), sa.Column("issuer", sa.String(500), nullable=True), - sa.Column("trusted_domains", sa.JSON(), nullable=False, server_default=sa.text("'[]'")), # JSON type for proper validation + sa.Column("trusted_domains", sa.JSON(), nullable=True), # JSON type for proper validation sa.Column("scope", sa.String(200), nullable=False, server_default=sa.text("'openid profile email'")), sa.Column("auto_create_users", sa.Boolean, nullable=False, server_default=sa.true()), - sa.Column("team_mapping", sa.JSON(), nullable=False, server_default=sa.text("'{}'")), # JSON type for proper validation + sa.Column("team_mapping", sa.JSON(), nullable=True), # JSON type for proper validation sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), ) diff --git a/mcpgateway/alembic/versions/e4fc04d1a442_add_annotations_to_tables.py b/mcpgateway/alembic/versions/e4fc04d1a442_add_annotations_to_tables.py index f3dfaf445..c09881fe7 100644 --- a/mcpgateway/alembic/versions/e4fc04d1a442_add_annotations_to_tables.py +++ b/mcpgateway/alembic/versions/e4fc04d1a442_add_annotations_to_tables.py @@ -44,7 +44,7 @@ def upgrade() -> None: columns = [col["name"] for col in inspector.get_columns("tools")] if "annotations" not in columns: try: - op.add_column("tools", sa.Column("annotations", sa.JSON(), server_default=sa.text("'{}'"), nullable=False)) + op.add_column("tools", sa.Column("annotations", sa.JSON(), nullable=True)) except Exception as e: print(f"Warning: Could not add annotations column to tools: {e}") diff --git a/mcpgateway/db.py b/mcpgateway/db.py index a91efe934..41e5c5a30 100644 --- a/mcpgateway/db.py +++ b/mcpgateway/db.py @@ -1231,15 +1231,15 @@ def reject(self, admin_email: str, reason: str, notes: Optional[str] = None) -> server_tool_association = Table( "server_tool_association", Base.metadata, - Column("server_id", String, ForeignKey("servers.id"), primary_key=True), - Column("tool_id", String, ForeignKey("tools.id"), primary_key=True), + Column("server_id", String(36), ForeignKey("servers.id"), primary_key=True), + Column("tool_id", String(36), ForeignKey("tools.id"), primary_key=True), ) # Association table for servers and resources server_resource_association = Table( "server_resource_association", Base.metadata, - Column("server_id", String, ForeignKey("servers.id"), primary_key=True), + Column("server_id", String(36), ForeignKey("servers.id"), primary_key=True), Column("resource_id", Integer, ForeignKey("resources.id"), primary_key=True), ) @@ -1247,7 +1247,7 @@ def reject(self, admin_email: str, reason: str, notes: Optional[str] = None) -> server_prompt_association = Table( "server_prompt_association", Base.metadata, - Column("server_id", String, ForeignKey("servers.id"), primary_key=True), + Column("server_id", String(36), ForeignKey("servers.id"), primary_key=True), Column("prompt_id", Integer, ForeignKey("prompts.id"), primary_key=True), ) @@ -1255,8 +1255,8 @@ def reject(self, admin_email: str, reason: str, notes: Optional[str] = None) -> server_a2a_association = Table( "server_a2a_association", Base.metadata, - Column("server_id", String, ForeignKey("servers.id"), primary_key=True), - Column("a2a_agent_id", String, ForeignKey("a2a_agents.id"), primary_key=True), + Column("server_id", String(36), ForeignKey("servers.id"), primary_key=True), + Column("a2a_agent_id", String(36), ForeignKey("a2a_agents.id"), primary_key=True), ) @@ -1292,7 +1292,7 @@ class ToolMetric(Base): __tablename__ = "tool_metrics" id: Mapped[int] = mapped_column(primary_key=True) - tool_id: Mapped[str] = mapped_column(String, ForeignKey("tools.id"), nullable=False) + tool_id: Mapped[str] = mapped_column(String(36), ForeignKey("tools.id"), nullable=False) timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) response_time: Mapped[float] = mapped_column(Float, nullable=False) is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) @@ -1344,7 +1344,7 @@ class ServerMetric(Base): __tablename__ = "server_metrics" id: Mapped[int] = mapped_column(primary_key=True) - server_id: Mapped[str] = mapped_column(String, ForeignKey("servers.id"), nullable=False) + server_id: Mapped[str] = mapped_column(String(36), ForeignKey("servers.id"), nullable=False) timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) response_time: Mapped[float] = mapped_column(Float, nullable=False) is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) @@ -1402,7 +1402,7 @@ class A2AAgentMetric(Base): response_time: Mapped[float] = mapped_column(Float, nullable=False) is_success: Mapped[bool] = mapped_column(Boolean, nullable=False) error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - interaction_type: Mapped[str] = mapped_column(String, nullable=False, default="invoke") + interaction_type: Mapped[str] = mapped_column(String(50), nullable=False, default="invoke") # Relationship back to the A2AAgent model. a2a_agent: Mapped["A2AAgent"] = relationship("A2AAgent", back_populates="metrics") @@ -1443,11 +1443,11 @@ class Tool(Base): __tablename__ = "tools" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) - original_name: Mapped[str] = mapped_column(String, nullable=False) - url: Mapped[str] = mapped_column(String, nullable=True) - description: Mapped[Optional[str]] - integration_type: Mapped[str] = mapped_column(default="MCP") - request_type: Mapped[str] = mapped_column(default="SSE") + original_name: Mapped[str] = mapped_column(String(255), nullable=False) + url: Mapped[str] = mapped_column(String(767), nullable=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + integration_type: Mapped[str] = mapped_column(String(20), default="MCP") + request_type: Mapped[str] = mapped_column(String(20), default="SSE") headers: Mapped[Optional[Dict[str, str]]] = mapped_column(JSON) input_schema: Mapped[Dict[str, Any]] = mapped_column(JSON) annotations: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, default=lambda: {}) @@ -1455,32 +1455,32 @@ class Tool(Base): updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) enabled: Mapped[bool] = mapped_column(default=True) reachable: Mapped[bool] = mapped_column(default=True) - jsonpath_filter: Mapped[str] = mapped_column(default="") + jsonpath_filter: Mapped[str] = mapped_column(Text, default="") tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) # Request type and authentication fields - auth_type: Mapped[Optional[str]] = mapped_column(default=None) # "basic", "bearer", or None - auth_value: Mapped[Optional[str]] = mapped_column(default=None) + auth_type: Mapped[Optional[str]] = mapped_column(String(20), default=None) # "basic", "bearer", or None + auth_value: Mapped[Optional[str]] = mapped_column(Text, default=None) # custom_name,custom_name_slug, display_name - custom_name: Mapped[Optional[str]] = mapped_column(String, nullable=False) - custom_name_slug: Mapped[Optional[str]] = mapped_column(String, nullable=False) - display_name: Mapped[Optional[str]] = mapped_column(String, nullable=True) + custom_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=False) + custom_name_slug: Mapped[Optional[str]] = mapped_column(String(255), nullable=False) + display_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # Federation relationship with a local gateway gateway_id: Mapped[Optional[str]] = mapped_column(ForeignKey("gateways.id")) @@ -1503,7 +1503,7 @@ class Tool(Base): # def gateway_slug(self) -> str: # return self.gateway.slug - _computed_name = Column("name", String, unique=True) # Stored column + _computed_name = Column("name", String(255), unique=True) # Stored column @hybrid_property def name(self): @@ -1723,30 +1723,30 @@ class Resource(Base): __tablename__ = "resources" id: Mapped[int] = mapped_column(primary_key=True) - uri: Mapped[str] = mapped_column(unique=True) - name: Mapped[str] - description: Mapped[Optional[str]] - mime_type: Mapped[Optional[str]] - size: Mapped[Optional[int]] - template: Mapped[Optional[str]] # URI template for parameterized resources + uri: Mapped[str] = mapped_column(String(767), unique=True) + name: Mapped[str] = mapped_column(String(255), nullable=False) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + mime_type: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + size: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + template: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # URI template for parameterized resources created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) is_active: Mapped[bool] = mapped_column(default=True) tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) metrics: Mapped[List["ResourceMetric"]] = relationship("ResourceMetric", back_populates="resource", cascade="all, delete-orphan") @@ -1933,9 +1933,9 @@ class ResourceSubscription(Base): id: Mapped[int] = mapped_column(primary_key=True) resource_id: Mapped[int] = mapped_column(ForeignKey("resources.id")) - subscriber_id: Mapped[str] # Client identifier + subscriber_id: Mapped[str] = mapped_column(String(255), nullable=False) # Client identifier created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) - last_notification: Mapped[Optional[datetime]] = mapped_column(DateTime) + last_notification: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) resource: Mapped["Resource"] = relationship(back_populates="subscriptions") @@ -1961,8 +1961,8 @@ class Prompt(Base): __tablename__ = "prompts" id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(unique=True) - description: Mapped[Optional[str]] + name: Mapped[str] = mapped_column(String(255), unique=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) template: Mapped[str] = mapped_column(Text) argument_schema: Mapped[Dict[str, Any]] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) @@ -1971,18 +1971,18 @@ class Prompt(Base): tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) metrics: Mapped[List["PromptMetric"]] = relationship("PromptMetric", back_populates="prompt", cascade="all, delete-orphan") @@ -2151,27 +2151,27 @@ class Server(Base): __tablename__ = "servers" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) - name: Mapped[str] = mapped_column(unique=True) - description: Mapped[Optional[str]] - icon: Mapped[Optional[str]] + name: Mapped[str] = mapped_column(String(255), unique=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + icon: Mapped[Optional[str]] = mapped_column(String(767), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) is_active: Mapped[bool] = mapped_column(default=True) tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) metrics: Mapped[List["ServerMetric"]] = relationship("ServerMetric", back_populates="server", cascade="all, delete-orphan") @@ -2306,32 +2306,32 @@ class Gateway(Base): __tablename__ = "gateways" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) - name: Mapped[str] = mapped_column(String, nullable=False) - slug: Mapped[str] = mapped_column(String, nullable=False, unique=True) - url: Mapped[str] = mapped_column(String, unique=True) - description: Mapped[Optional[str]] - transport: Mapped[str] = mapped_column(default="SSE") + name: Mapped[str] = mapped_column(String(255), nullable=False) + slug: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) + url: Mapped[str] = mapped_column(String(767), unique=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + transport: Mapped[str] = mapped_column(String(20), default="SSE") capabilities: Mapped[Dict[str, Any]] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) enabled: Mapped[bool] = mapped_column(default=True) reachable: Mapped[bool] = mapped_column(default=True) - last_seen: Mapped[Optional[datetime]] + last_seen: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) # Header passthrough configuration @@ -2356,7 +2356,7 @@ class Gateway(Base): # federated_prompts: Mapped[List["Prompt"]] = relationship(secondary=prompt_gateway_table, back_populates="federated_with") # Authorizations - auth_type: Mapped[Optional[str]] = mapped_column(default=None) # "basic", "bearer", "headers", "oauth" or None + auth_type: Mapped[Optional[str]] = mapped_column(String(20), default=None) # "basic", "bearer", "headers", "oauth" or None auth_value: Mapped[Optional[Dict[str, str]]] = mapped_column(JSON) # OAuth configuration @@ -2421,17 +2421,17 @@ class A2AAgent(Base): __tablename__ = "a2a_agents" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) - name: Mapped[str] = mapped_column(String, nullable=False, unique=True) - slug: Mapped[str] = mapped_column(String, nullable=False, unique=True) + name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) + slug: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) description: Mapped[Optional[str]] = mapped_column(Text) - endpoint_url: Mapped[str] = mapped_column(String, nullable=False) - agent_type: Mapped[str] = mapped_column(String, nullable=False, default="generic") # e.g., "openai", "anthropic", "custom" - protocol_version: Mapped[str] = mapped_column(String, nullable=False, default="1.0") + endpoint_url: Mapped[str] = mapped_column(String(767), nullable=False) + agent_type: Mapped[str] = mapped_column(String(50), nullable=False, default="generic") # e.g., "openai", "anthropic", "custom" + protocol_version: Mapped[str] = mapped_column(String(10), nullable=False, default="1.0") capabilities: Mapped[Dict[str, Any]] = mapped_column(JSON, default=dict) # Configuration and authentication config: Mapped[Dict[str, Any]] = mapped_column(JSON, default=dict) - auth_type: Mapped[Optional[str]] = mapped_column(String) # "api_key", "oauth", "bearer", etc. + auth_type: Mapped[Optional[str]] = mapped_column(String(50)) # "api_key", "oauth", "bearer", etc. auth_value: Mapped[Optional[str]] = mapped_column(Text) # encrypted auth data # Status and metadata @@ -2445,18 +2445,18 @@ class A2AAgent(Base): tags: Mapped[List[str]] = mapped_column(JSON, default=list, nullable=False) # Comprehensive metadata for audit tracking - created_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - created_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + created_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + created_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + created_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) created_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - modified_by: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_from_ip: Mapped[Optional[str]] = mapped_column(String, nullable=True) - modified_via: Mapped[Optional[str]] = mapped_column(String, nullable=True) + modified_by: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + modified_from_ip: Mapped[Optional[str]] = mapped_column(String(45), nullable=True) + modified_via: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) modified_user_agent: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - import_batch_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) - federation_source: Mapped[Optional[str]] = mapped_column(String, nullable=True) + import_batch_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True) + federation_source: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) # Team scoping fields for resource organization @@ -2547,10 +2547,10 @@ class SessionRecord(Base): __tablename__ = "mcp_sessions" - session_id: Mapped[str] = mapped_column(primary_key=True) + session_id: Mapped[str] = mapped_column(String(255), primary_key=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # pylint: disable=not-callable last_accessed: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) # pylint: disable=not-callable - data: Mapped[str] = mapped_column(String, nullable=True) + data: Mapped[str] = mapped_column(Text, nullable=True) messages: Mapped[List["SessionMessageRecord"]] = relationship("SessionMessageRecord", back_populates="session", cascade="all, delete-orphan") @@ -2561,8 +2561,8 @@ class SessionMessageRecord(Base): __tablename__ = "mcp_messages" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - session_id: Mapped[str] = mapped_column(ForeignKey("mcp_sessions.session_id")) - message: Mapped[str] = mapped_column(String, nullable=True) + session_id: Mapped[str] = mapped_column(String(255), ForeignKey("mcp_sessions.session_id")) + message: Mapped[str] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # pylint: disable=not-callable last_accessed: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) # pylint: disable=not-callable diff --git a/pyproject.toml b/pyproject.toml index 88f44e66a..8eedc2b51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,10 @@ postgres = [ "psycopg2-binary>=2.9.10", ] +mysql = [ + "pymysql>=1.1.0", +] + # Fuzzing and property-based testing fuzz = [ "hypothesis>=6.138.14",