Skip to content

Deployment

Mert Cobanov edited this page Aug 1, 2026 · 1 revision

Deployment

Two transports. Pick by where the client runs.

  • stdio — the client launches the server as a subprocess. Local clients like Claude Desktop and Cursor. No network, no auth needed.
  • HTTP — the server runs somewhere and clients connect over the network. Set AUTH_TOKEN.

Docker Compose

cp env.example .env
# set DATABASE_URL, and AUTH_TOKEN if this is reachable by anything but you
docker compose up -d

The MCP endpoint is http://localhost:8888/mcp and the health probe is http://localhost:8888/health.

Prebuilt images

Multi-arch images (linux/amd64, linux/arm64) are published to GHCR on every tagged release:

docker run -d --name teslamate-mcp \
  -e DATABASE_URL='postgresql://teslamate:…@db-host:5432/teslamate' \
  -e AUTH_TOKEN='' \
  -p 8888:8888 \
  ghcr.io/cobanov/teslamate-mcp:latest

Pin a version (:0.10.0, :0.10) rather than :latest for anything you depend on.

Running next to TeslaMate

If TeslaMate itself runs in Docker on the same host, the simplest arrangement is a compose file beside it. TeslaMate typically publishes its database on host port 5433, so from a container on the same host:

services:
  teslamate-mcp:
    image: ghcr.io/cobanov/teslamate-mcp:latest
    container_name: teslamate-mcp
    restart: unless-stopped
    ports:
      - "8888:8888"
    environment:
      DATABASE_URL: postgresql://teslamate:PASSWORD@HOST:5433/teslamate
      AUTH_TOKEN: ${AUTH_TOKEN}
      REPORT_TIMEZONE: Europe/Istanbul

Joining TeslaMate's own compose network instead lets you use the internal hostname and port (@database:5432) and avoids publishing the database port at all.

Connecting a client

stdio — point the client at the checkout:

{
  "mcpServers": {
    "teslamate": {
      "command": "uv",
      "args": ["--directory", "/path/to/teslamate-mcp", "run", "teslamate-mcp", "stdio"]
    }
  }
}

The server reads .env from its working directory, which is why --directory matters.

HTTP — give the client the endpoint and the bearer token:

{
  "mcpServers": {
    "teslamate": {
      "url": "http://your-host:8888/mcp",
      "headers": { "Authorization": "Bearer YOUR_TOKEN" }
    }
  }
}

Both /mcp and /mcp/ are served directly, so a client that saved either form keeps working.

Exposure

The database behind this holds every trip you have taken, with coordinates and timestamps. Think about that before putting it on the public internet.

Safer options, roughly in order:

  1. Private network only — a VPN or an overlay network such as Tailscale. The port is never published publicly, and AUTH_TOKEN is a second layer rather than the only one.
  2. Reverse proxy with an identity layer in front — Cloudflare Access, an OAuth proxy, or mTLS.
  3. Public with only a bearer token — workable, but the token is then the single thing between the internet and your location history. Use a long random one from teslamate-mcp gen-token, and rotate it if it ever lands in a config file you shared.

Health checks

GET /health performs a real database round-trip and returns 503 when the database is unreachable, so orchestrators see a container that cannot serve as unhealthy. The image's built-in HEALTHCHECK already uses it.

curl -s http://localhost:8888/health
# {"status":"ok","version":"0.10.0","database":"ok"}

Troubleshooting

Container is healthy but every call fails — should not happen since the probe checks the database, but if /health reports "status": "degraded", read the detail field. It is usually a DATABASE_URL pointing at localhost from inside a container, where localhost is the container itself.

A tool call hangs — every query is bounded by STATEMENT_TIMEOUT_MS (30s by default). If you are hitting it on a large database, the query is the problem; open an issue with the tool name.

401 Unauthorized — the client is not sending Authorization: Bearer …, or AUTH_TOKEN differs between server and client. /health is exempt from auth, so a working /health and a failing /mcp points here.

Clone this wiki locally