-
Notifications
You must be signed in to change notification settings - Fork 19
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.
cp env.example .env
# set DATABASE_URL, and AUTH_TOKEN if this is reachable by anything but you
docker compose up -dThe MCP endpoint is http://localhost:8888/mcp and the health probe is http://localhost:8888/health.
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:latestPin a version (:0.10.0, :0.10) rather than :latest for anything you depend on.
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/IstanbulJoining TeslaMate's own compose network instead lets you use the internal hostname and port (@database:5432) and avoids publishing the database port at all.
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.
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:
-
Private network only — a VPN or an overlay network such as Tailscale. The port is never published publicly, and
AUTH_TOKENis a second layer rather than the only one. - Reverse proxy with an identity layer in front — Cloudflare Access, an OAuth proxy, or mTLS.
-
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.
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"}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.