A self-hosted AI agent that connects to any OpenAI-compatible LLM (LM Studio, Ollama, etc.), gains capabilities via MCP servers, communicates through Discord, runs scheduled tasks, and provides a web dashboard for monitoring.
# 1. Clone and enter the repo.
git clone <your-repo-url> && cd jarvis
# 2. Copy example configs and fill in your values.
cp .env.example .env
cp config/jarvis.yaml.example config/jarvis.yaml
cp config/channels.yaml.example config/channels.yaml
cp config/mcp-servers.yaml.example config/mcp-servers.yaml
# 3. Edit .env with your LLM endpoint.
# If running LM Studio / Ollama on the host:
# JARVIS_LLM_BASE_URL=http://host.docker.internal:1234/v1
# 4. Create the data directory.
mkdir -p data
# 5. Start Jarvis.
docker compose up -d
# 6. Open the dashboard.
open http://localhost:8080Requires Python 3.12 and uv.
# Install dependencies.
uv sync
# Copy example configs.
cp config/jarvis.yaml.example config/jarvis.yaml
# Edit config/jarvis.yaml with your LLM endpoint.
mkdir -p data
# Run migrations.
uv run alembic upgrade head
# Start the service.
uv run python -m jarvis serveProduction runs a pre-built image from GitHub Container Registry (ghcr.io). You
build and push from a dev machine; the server pulls and runs it. The image is
multi-arch (linux/amd64 + linux/arm64), so it runs on x86 or ARM hosts.
Database migrations run automatically on container start (see entrypoint.sh).
make check # lint + tests
export CR_PAT=ghp_xxx # GitHub PAT with the write:packages scope
make login # docker login ghcr.io
make deploy # build multi-arch + push :<git-sha> and :latestmake deploy pushes ghcr.io/mdolton/jarvis:<git-sha> and :latest. The
package is private by default — either keep it private and docker login
on the server, or make it public in the GitHub package settings after the first
push.
The server needs Docker, docker-compose.prod.yml, the config/ directory,
and a .env. Clone the repo (or copy those files), then:
# Configure.
cp .env.example .env # set JARVIS_BASE_URL=https://jarvis.moltonlava.online,
# JARVIS_SECRETS_KEY, GOOGLE_OAUTH_* etc.
cp config/jarvis.yaml.example config/jarvis.yaml # + channels/mcp-servers as needed
mkdir -p data
# If the ghcr package is private, authenticate to pull:
export CR_PAT=ghp_xxx
make login
# Pull and start.
make prod-pull
make prod-upOn Linux, the bind-mounted ./data must be writable by the container's user, or
SQLite fails with unable to open database file. The container runs as
JARVIS_UID:JARVIS_GID (set in .env, default 1000:1000) — set these to the
owner of ./data (id -u / id -g). If ./data is already root-owned, fix it
with sudo chown -R "$(id -u):$(id -g)" data. (Docker Desktop on macOS ignores
ownership, so this only bites on a real Linux host.)
The dashboard listens on port 8080. For https://jarvis.moltonlava.online
(required for the Google OAuth redirect URI to work), terminate TLS at your
reverse proxy and forward to the container's port 8080.
# Dev machine:
make deploy
# Server:
make prod-pull && make prod-upPin a specific build instead of latest by exporting JARVIS_IMAGE_TAG (e.g.
the git short SHA from make deploy) before make prod-pull/make prod-up.
# Run a one-shot agent invocation.
python -m jarvis invoke "What's on my calendar today?"
# Validate config and print a summary.
python -m jarvis check-config
# Start as a long-lived service (Discord + scheduler + dashboard).
python -m jarvis serveAll config lives in the config/ directory:
| File | Purpose |
|---|---|
jarvis.yaml |
LLM endpoint, timezone, concurrency, log level |
channels.yaml |
Discord bot token and allowed user IDs |
mcp-servers.yaml |
MCP server definitions (stdio, HTTP, SSE) |
Environment variables in YAML files are expanded via ${VAR} syntax. Secrets should go in .env (never committed) and be referenced in YAML.
Jarvis gains capabilities by connecting to MCP servers. Configure them in mcp-servers.yaml:
servers:
- name: filesystem
transport: stdio
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"]
- name: calendar
transport: http
url: http://localhost:3000/mcpCreate schedules via the dashboard at /schedules. Each schedule has:
- A cron expression (e.g.,
0 8 * * *for 8am daily) - A prompt (what to tell the agent)
- An output mode:
discord(DM you),dashboard_only(silent), ordiscord_if_noteworthy(agent decides)
Available at http://localhost:8080 when running:
- Home — service status overview
- Conversations — browse past agent interactions
- Schedules — create, enable/disable, delete schedules
- MCP — connected servers and discovered tools
- Audit — full event log with live SSE tailing
- Settings — read-only config view
Single Python async process running in Docker:
- Agent: OpenAI Agents SDK with MCP tool integration
- Channels: Discord DM adapter (Slack/WhatsApp extensible)
- Scheduler: APScheduler cron-based triggers
- Dashboard: FastAPI + HTMX + Jinja2
- Persistence: SQLite (WAL mode) + Alembic migrations
- Audit: Buffered async logger capturing every LLM call, tool call, and agent decision
Jarvis connects to OAuth-protected HTTP MCP servers from the /mcp dashboard. Two
providers ship in the catalog:
- Fastmail — Dynamic Client Registration (DCR); no manual client setup.
- Gmail — Google's official Gmail MCP server (
https://gmailmcp.googleapis.com/mcp/v1). Google does not support DCR, so you create the OAuth client by hand and supply its credentials via environment variables.
- Generate a Fernet secrets key:
uv run python -c "from jarvis.oauth.crypto import generate_key; print(generate_key())" - Set the base env vars (in
.envfor Docker, or your shell for local dev):JARVIS_SECRETS_KEY=<paste-the-key> JARVIS_BASE_URL=https://jarvis.moltonlava.online # or http://localhost:8080 locally
In Google Cloud Console:
- Enable the Gmail API and request access to the Gmail MCP server (early access).
- Configure the OAuth consent screen with scopes
https://www.googleapis.com/auth/gmail.readonlyandhttps://www.googleapis.com/auth/gmail.compose. While the app is unverified, add your Google account as a test user. - Create an OAuth client ID of type Web application:
- Authorized redirect URI:
https://jarvis.moltonlava.online/oauth/callback(must equal${JARVIS_BASE_URL}/oauth/callbackexactly). - Authorized JavaScript origins: leave empty — Jarvis uses a server-side code flow.
- Authorized redirect URI:
Then add the client credentials to Jarvis's environment:
GOOGLE_OAUTH_CLIENT_ID=<from Google Cloud Console>
GOOGLE_OAUTH_CLIENT_SECRET=<from Google Cloud Console>
Restart Jarvis, open /mcp, and click Connect on the Gmail card. Jarvis stores the
encrypted tokens and refreshes them automatically; if a refresh permanently fails the card
shows Needs re-auth with a Reconnect button.
MIT