Self-hosting Guide

This guide covers running the Spekta MCP server locally using Docker Compose. Self-hosting is useful for development, air-gapped environments, or if you want full control over your data.

Note: The Next.js frontend (dashboard, provider management) is a separate application. This guide covers the MCP server only.


Prerequisites


Quick start

# 1. Clone the repo
git clone https://github.com/nanocorp-hq/spekta.git
cd spekta/spekta-mcp

# 2. Copy the example env file
cp .env.example .env

# 3. Edit .env — at minimum, set a strong SECRET_KEY
nano .env

# 4. Start all services (app + postgres + redis)
docker compose up -d

# 5. Wait for Postgres to be ready, then apply migrations
docker compose exec app python migrations/seed.py   # optional: load demo data

# 6. Verify health
curl http://localhost:8080/health
# → {"status":"ok","db":"ok","version":"1.0.0"}

The MCP SSE endpoint is now live at http://localhost:8080/mcp/sse.


Docker Compose services

The spekta-mcp/docker-compose.yml defines three services:

ServiceImageDefault portRole
appBuilt from ./Dockerfile8080FastAPI MCP server
postgrespostgres:165432Primary database
redisredis:7-alpine6379Rate-limit counters (optional)

Starting and stopping

# Start (detached)
docker compose up -d

# Watch logs
docker compose logs -f app

# Stop (keep volumes)
docker compose stop

# Stop and remove containers + volumes
docker compose down -v

Environment variables reference

Set these in spekta-mcp/.env (or pass as environment variables in your orchestration platform).

Required

VariableExampleDescription
DATABASE_URLpostgresql://spekta:spekta@postgres:5432/spektaPostgreSQL connection string. Use the service name postgres when running inside Compose.
SECRET_KEYchange-me-in-production-32chars+Used for session token signing. Generate with openssl rand -hex 32.

Optional — application behaviour

VariableDefaultDescription
LOG_LEVELINFOLogging verbosity: DEBUG, INFO, WARNING, ERROR
PORT8080Port the Uvicorn server listens on
WORKERS1Uvicorn worker count. For production, set to (2 × CPU cores) + 1.
REDIS_URL(none)Redis connection string for distributed rate limiting. If unset, rate limiting falls back to in-memory (not suitable for multi-worker deployments).
RATE_LIMIT_PER_MINUTE100Global default requests/minute per API key (overridden by tenant plan).

Optional — observability

VariableDefaultDescription
SENTRY_DSN(none)Sentry DSN for error tracking. Leave empty to disable Sentry.
SENTRY_ENVIRONMENTproductionSentry environment tag
SENTRY_TRACES_SAMPLE_RATE0.1Fraction of requests to trace (0.0–1.0)

Optional — CORS

VariableDefaultDescription
CORS_ORIGINS*Comma-separated list of allowed origins. Set to your dashboard URL in production: https://app.your-domain.com.

Database setup

Schema migrations

All migrations/*.sql files are automatically applied by Postgres on first start (via docker-entrypoint-initdb.d). If you are connecting to an external Postgres instance, apply them in filename order:

for migration in spekta-mcp/migrations/*.sql; do
  psql "$DATABASE_URL" < "$migration"
done

Seeding demo data

The seed script creates three demo tenants with events, ticket sales, and API keys:

# Inside Docker Compose
docker compose exec app python migrations/seed.py

# Against an external DB
DATABASE_URL="postgresql://..." python spekta-mcp/migrations/seed.py

After seeding, three demo API keys are printed:

tix_live_festival_hub_gmbh_0    → tenant: Festival Hub GmbH      (plan: business)
tix_live_cityevents_ltd_1       → tenant: City Events Ltd         (plan: pro)
tix_live_indie_shows_co_2       → tenant: Indie Shows Co          (plan: free)

Connecting to the database

# Via Docker Compose
docker compose exec postgres psql -U spekta spekta

# Directly (requires psql installed locally)
psql postgresql://spekta:spekta@localhost:5432/spekta

Connecting Claude Desktop to the local server

Update ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) to point at localhost:

{
  "mcpServers": {
    "spekta-local": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:8080/mcp/sse"],
      "env": {
        "X-Spekta-API-Key": "tix_live_festival_hub_gmbh_0"
      }
    }
  }
}

Restart Claude Desktop. You should see the Spekta tools available in the toolbar.


Production considerations

TLS / HTTPS

The Docker Compose setup does not terminate TLS. In production, place a reverse proxy (nginx, Caddy, Traefik) in front of the app service. Example with Caddy:

spekta.your-domain.com {
    reverse_proxy app:8080
}

Scaling workers

Increase WORKERS in .env or use --scale with Compose. For more than 2 workers, set REDIS_URL to ensure rate limits are shared across processes.

Credential encryption

The Next.js sync engine encrypts provider credentials with ENCRYPTION_KEY. The MCP server does not handle provider credentials directly — it reads already-synced data from the database. No additional encryption config is needed for the MCP server.

Backup

Back up the postgres_data Docker volume regularly. The volume contains all synced event data. The volume name is spekta-mcp_postgres_data by default (prefixed with the Compose project name).

# One-time snapshot
docker run --rm \
  -v spekta-mcp_postgres_data:/data \
  -v $(pwd)/backups:/backup \
  postgres:16 \
  pg_dump -U spekta -h localhost spekta > /backup/spekta-$(date +%Y%m%d).sql

Updating

git pull origin main
docker compose build app
docker compose up -d --no-deps app

Check the CHANGELOG.md before updating — breaking schema changes require running new migration files.


Troubleshooting

SymptomLikely causeFix
{"status":"ok","db":"error"}Postgres not ready or wrong DATABASE_URLCheck docker compose logs postgres
401 UnauthorizedInvalid or revoked API keyRe-run seed or generate a key via the dashboard
429 Too Many RequestsRate limit hitWait 60 s or increase RATE_LIMIT_PER_MINUTE
MCP tools not visible in Claude DesktopConfig path wrong or JSON syntax errorValidate JSON at jsonlint.com; restart Claude Desktop
ModuleNotFoundError on startupPython dependencies missingRebuild: docker compose build --no-cache app