Skip to main content
Glama
cobanov

teslamate-mcp

by cobanov
README.md
<div align="center">

# TeslaMate MCP Server

<img src="assets/teslamcp.gif" alt="TeslaMate MCP Server demo" width="720" />

Connect your AI assistant to your [TeslaMate](https://github.com/teslamate-org/teslamate) data.
This is a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server. It reads your TeslaMate PostgreSQL database. It gives MCP clients (Claude Desktop, Cursor, and others) 57 tools, 11 prompts, and interactive charts.

[![CI](https://github.com/cobanov/teslamate-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/cobanov/teslamate-mcp/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/cobanov/teslamate-mcp?logo=github&sort=semver)](https://github.com/cobanov/teslamate-mcp/releases)
[![GHCR](https://img.shields.io/badge/ghcr.io-cobanov%2Fteslamate--mcp-2496ED?logo=docker)](https://github.com/cobanov/teslamate-mcp/pkgs/container/teslamate-mcp)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue?logo=python&logoColor=white)](https://www.python.org/downloads/)
[![License](https://img.shields.io/github/license/cobanov/teslamate-mcp)](LICENSE)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

**[Documentation](https://github.com/cobanov/teslamate-mcp/wiki)** · [Tool reference](https://github.com/cobanov/teslamate-mcp/wiki/Tool-Reference) · [Configuration](https://github.com/cobanov/teslamate-mcp/wiki/Configuration) · [Deployment](https://github.com/cobanov/teslamate-mcp/wiki/Deployment)

</div>

## What you can ask

- "Is my battery really degrading?" — the server estimates usable capacity in kWh from your charging sessions.
- "How much range do I lose while parked?" — the server finds vampire drain between drives.
- "Show the route of my longest drive." — the server returns the GPS track, with an interactive map on chart-capable clients.
- "Compare my driving this month with last month." — one call returns both windows, per metric.
- "Can I reach Ankara (450 km) at 5°C without charging?" — the server estimates the arrival battery % from your own drives at that temperature.
- "Why isn't my car sleeping?" — the server reads TeslaMate's state log and lists the periods the car stayed awake while parked.
- "What did my car do on Saturday?" — one call returns the drives, charges, and parked time in order.

## Highlights

- **47 SQL report tools.** Battery capacity and degradation, sleep and state history, a timeline of each day, trips, range estimates, fast-charging speeds, charging costs (with estimates for missing ones) and fuel savings, elevation, vampire drain, geofences, driving patterns, routes, and search tools for drives and charging sessions.
- **8 interactive charts (MCP Apps).** Charging curves (one session, or several overlaid), battery degradation, drive and trip maps with elevation profiles, a monthly or yearly activity dashboard, a map of visited places, and consumption against temperature. On clients without chart support, the same tools return plain rows.
- **Guidance for the assistant.** MCP instructions and 11 prompts map questions to tools, and `get_unit_preferences` lets the assistant answer in the units set in TeslaMate.
- **Typed inputs and outputs.** Each report tool declares its parameters and result columns in a `.toml` file. The server validates the declarations at startup. Parameters bind as SQL placeholders, never as string concatenation.
- **Safe custom SQL.** `run_sql` runs your `SELECT` inside a PostgreSQL `READ ONLY` transaction, with timeouts and a row cap. The transaction always rolls back.
- **Optional cost writes, with confirmation.** One opt-in tool writes charging costs. A column-scoped database grant limits what it can touch. Clients with MCP elicitation show a confirmation dialog first.
- **Local-time reports.** Set `REPORT_TIMEZONE` and daily, weekly, and monthly buckets follow your local midnight.
- **Two transports.** Run `teslamate-mcp stdio` for local clients. Run `teslamate-mcp http` for remote clients, with bearer-token auth and a `/health` probe.

## Requirements

- TeslaMate with its PostgreSQL database.
- Python 3.11+ for a local install, or Docker for a server install.

## Quick start — local (stdio)

1. Install:

   ```bash
   git clone https://github.com/cobanov/teslamate-mcp.git
   cd teslamate-mcp
   cp env.example .env      # set DATABASE_URL
   uv sync
   ```

2. Add the server to your MCP client. Example for Claude Desktop or Cursor:

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

## Quick start — Docker

```bash
cp env.example .env          # set DATABASE_URL, and set AUTH_TOKEN for remote use
docker compose up -d
```

The MCP endpoint is `http://localhost:8888/mcp`. The health probe is `http://localhost:8888/health`. It runs a `SELECT 1` against the database and returns `503` when the database is unreachable.

A multi-arch image (`linux/amd64`, `linux/arm64`) is published to GHCR on each release:

```bash
docker run --rm -e DATABASE_URL=... -p 8888:8888 ghcr.io/cobanov/teslamate-mcp:latest
```

## Deploy on Unraid, behind Cloudflare

You can reach this server from claude.ai, Claude Desktop, and mobile — with OAuth login and with no open ports on your home network.

The **[Unraid + Cloudflare deployment guide](deploy/unraid/UNRAID_CLOUDFLARE_DEPLOYMENT.md)** shows the full path:

1. Run the container on Unraid with the included [Community Apps template](deploy/unraid/teslamate-mcp.xml).
2. Publish it through a Cloudflare Tunnel. No inbound firewall rules are necessary.
3. Put a Cloudflare Zero Trust **MCP Server Portal** in front. The portal handles OAuth for Claude clients and sends the bearer token upstream.

The guide includes the exact dashboard fields, verification commands for each phase, and a troubleshooting table.

> The portal rewrites `ui://` resource URIs, so the interactive charts (`show_*` tools) do not render through it. To get them, connect Claude through Cloudflare Access directly. The guide's [direct connection](deploy/unraid/UNRAID_CLOUDFLARE_DEPLOYMENT.md#phase-3b--direct-connection-for-interactive-charts) section shows how.

## Tools

Each report tool accepts optional filters: `car_name` everywhere, plus `days`, `limit`, and thresholds where they apply. A call with no arguments returns the full report.

Tools return metric values (km, km/h, °C, bar, m) and rated range. `get_unit_preferences` reads the units chosen in TeslaMate's settings (km or mi, °C or °F, bar or psi, rated or ideal range) so the assistant can convert when it answers; the charts stay metric.

The server also sends MCP `instructions`: how to read units, time zones and costs, and which tool answers which kind of question.

### Reports (22)

| Group | Tools |
|---|---|
| Vehicle | `get_basic_car_information`, `get_current_car_status`, `get_software_update_history`, `get_unit_preferences` |
| Battery | `get_battery_health_summary`, `get_battery_degradation_over_time`, `get_daily_battery_usage_patterns`, `get_tire_pressure_weekly_trends` |
| Driving | `get_monthly_driving_summary`, `get_daily_driving_patterns`, `get_longest_drives_by_distance`, `get_total_distance_and_efficiency`, `get_drive_summary_per_day`, `get_visited_places` |
| Efficiency | `get_efficiency_by_month_and_temperature`, `get_average_efficiency_by_temperature`, `get_efficiency_by_elevation`, `get_drive_efficiency_points`, `get_unusual_power_consumption` |
| Charging | `get_charging_by_location`, `get_all_charging_sessions_summary`, `get_most_visited_locations` |

### Insights (8)

| Tool | What it returns |
|---|---|
| `get_battery_capacity_trend` | Usable capacity in kWh, estimated from charging sessions (energy added ÷ SOC gained), per month and car |
| `get_vampire_drain` | Range lost while parked between drives; gaps that contain a charge do not count |
| `get_charging_efficiency` | kWh added vs kWh drawn per car, split into AC and DC |
| `get_charging_by_geofence` | Charging totals per TeslaMate geofence, plus an "Ungeofenced" group |
| `get_soc_hygiene` | Share of samples above 80% and below 20% state of charge |
| `get_period_comparison` | The last N days vs the N days before, one row per metric |
| `get_activity_report` | A month by day or a year by month: drives, distance, consumption, charging, and cost per bucket, empty days included |
| `get_trip_energy_estimate` | "Will I make it?": energy and battery % a drive of a given distance takes, from this car's own drives at a similar temperature (optionally motorway drives only), with a conservative figure and the arrival battery % |

### Timeline and car state (3)

TeslaMate logs whether each car is online, asleep, or offline. `get_current_car_status` reads that log too, and reports whether the car is driving, charging, online, asleep, or offline right now.

| Tool | What it returns |
|---|---|
| `get_timeline` | What the car did, in order: drives, charges, the time parked between them, and software updates, with places, battery %, kWh, and cost; answers "what did my car do on Saturday?" |
| `get_state_history` | Per car and local day: hours online, asleep, and offline, driving and charging hours, idle awake hours (online while parked and not charging), and wake-ups |
| `get_idle_awake_periods` | The online periods that kept the car awake while parked, longest first, with where it was parked; answers "why isn't my car sleeping?" |

### Search and detail (5)

| Tool | What it returns |
|---|---|
| `search_drives` | Drives filtered by date range, location text, distance, and car; sortable |
| `search_charging_sessions` | Charging sessions filtered by date range, location, energy, and car |
| `get_drive_details` | Full statistics for one drive |
| `get_drive_route` | GPS track points for one drive, downsampled |
| `get_charging_curve` | Power and SOC curve for one charging session, downsampled |

### Costs (3)

A session with no cost recorded counts as unknown, not free: averages per kWh use only the sessions that have a cost, and each report says how many sessions lack one.

| Tool | What it returns |
|---|---|
| `get_charging_costs` | Costs grouped by month, location, or car, with the number of sessions that have no cost |
| `get_charging_cost_estimates` | An estimated cost for each session with none: free Supercharging, a price you give, the geofence tariff, what you paid at the same place, or the typical price for that charger type |
| `get_fuel_savings` | What the same distance would have cost in fuel, against what charging cost, per car |

### Trips (3)

TeslaMate ends a drive every time the car parks, so a road trip with a WC break and a charging stop is several drives. The trip tools merge a car's consecutive drives while each stop in between stays short: up to `max_stop_minutes` (default 30) without charging, or `max_charging_stop_minutes` (default 120) when a charging session happened during the stop. Overnight stops end a trip.

| Tool | What it returns |
|---|---|
| `get_trips` | One row per trip: legs, stops and charging stops, distance, driving vs stopped time, moving speed, battery % at start and end, kWh added on the way; filter by date, car, distance, legs, or a place the trip passed through |
| `get_trip_details` | One trip's timeline: each drive, and each stop with where and how long, plus kWh and battery % for charging stops |
| `get_trip_route` | GPS track of a whole trip across its drives, downsampled, with the stop before each leg |

### Fast charging (3)

A session is DC when a sample came from a fast charger, or had no AC phases while power flowed; a Tesla-branded fast charger is a Supercharger.

| Tool | What it returns |
|---|---|
| `get_fast_charging_sessions` | DC sessions with peak power, average power, average power across 20–80% (the fair comparison), minutes from 20% to 80%, whether the battery heater ran, and cost |
| `get_fast_charging_by_location` | The same per location and charger type: which fast chargers are fastest and cheapest for your car |
| `get_charging_curve_comparison` | Power against battery % for several sessions, for overlaying their curves |

### Charts (8)

`show_charging_curve`, `show_charging_curve_comparison`, `show_battery_degradation`, `show_drive_route`, `show_trip_route`, `show_activity_report`, `show_visited_places`, and `show_efficiency_vs_temperature` are the chart versions of their `get_*` tools. On chart-capable clients they draw an interactive chart in the conversation. On other clients they return the same rows as the `get_*` tool. The route and trip maps draw the track over a low-detail basemap (Esri gray canvas, light or dark to match the client); the trip map also marks every stop, with charging stops highlighted. Both maps draw an elevation profile under the track; hovering it finds the place on the map. `show_activity_report` is a monthly or yearly dashboard, `show_visited_places` maps where the car parks (sized by arrivals, charging places highlighted), and `show_efficiency_vs_temperature` plots every drive's consumption against outside temperature with the 5 °C average. Set `MAP_TILES=false` to keep the maps fully offline.

### Custom (2)

- `get_database_schema` — lists all tables, shows the columns of one table, and re-reads the schema when you pass `refresh=true`.
- `run_sql` — runs one custom `SELECT` or `WITH … SELECT`.

### Prompts (11)

Ready-made workflows that name the tools to call, in order: `status_report`, `summarize_driving`, `analyze_battery_health`, `analyze_charging`, `find_anomalies`, `weather_efficiency`, `diagnose_sleep`, `plan_trip`, `review_trip`, `monthly_recap`, and `charging_costs_and_savings`. With cost writes on, `backfill_costs_from_receipts` joins them.

### Cost writes (opt-in, off by default)

Set `ENABLE_CHARGING_WRITES=true` to register `set_charging_cost(charging_process_id, cost)`. It sets the cost of one charging session. This is the same field that the TeslaMate UI edits. A `backfill_costs_from_receipts` prompt guides the receipt-matching workflow.

Give the database role write access to that single column only:

```sql
GRANT UPDATE (cost) ON charging_processes TO teslamate_ro;
```

This grant is the security boundary. The tool cannot write anything else. On clients with MCP elicitation, the user confirms each write in a dialog. `run_sql` stays read-only in all cases.

## Configuration

The server reads all settings from environment variables. It also reads a `.env` file. Only `DATABASE_URL` is required.

| Variable                | Default     | Purpose                                                     |
|-------------------------|-------------|-------------------------------------------------------------|
| `DATABASE_URL`          | _required_  | `postgresql://user:pass@host:5432/teslamate`                |
| `AUTH_TOKEN`            | _empty_     | Turns on bearer auth for the HTTP endpoint                  |
| `CF_ACCESS_TEAM_DOMAIN` | _empty_     | Cloudflare Access team domain; accepts Access-verified requests |
| `CF_ACCESS_AUD`         | _empty_     | Audience tag of that Access application (set with the above) |
| `HOST`                  | `0.0.0.0`   | HTTP bind host                                              |
| `PORT`                  | `8888`      | HTTP bind port                                              |
| `POOL_MIN_SIZE`         | `1`         | Minimum pool connections                                    |
| `POOL_MAX_SIZE`         | `10`        | Maximum pool connections                                    |
| `STATEMENT_TIMEOUT_MS`  | `30000`     | `statement_timeout` for every query, including reports      |
| `QUERY_TIMEOUT_MS`      | `5000`      | Tighter `statement_timeout` for `run_sql`                   |
| `CUSTOM_SQL_ROW_LIMIT`  | `1000`      | Most rows `run_sql` returns, whatever its `LIMIT`           |
| `REPORT_TIMEZONE`       | `UTC`       | IANA timezone for report buckets                            |
| `MAP_TILES`             | `true`      | Basemap under the route, trip, and places maps (Esri tiles) |
| `ENABLE_CHARGING_WRITES`| `false`     | Registers `set_charging_cost`                               |
| `LOG_LEVEL`             | `INFO`      | Python log level                                            |
| `DEBUG`                 | `false`     | Starlette debug mode; keep off in production                |

Generate a bearer token:

```bash
uv run teslamate-mcp gen-token
```

## Add your own query

No code change is necessary. The server finds new queries at startup.

1. Put a `SELECT` in `src/teslamate_mcp/queries/your_query.sql`.
2. Add `your_query.toml` next to it:

   ```toml
   name = "get_your_data"
   description = "What this returns, units, grouping, and available filters."

   [[params]]                 # optional: typed tool arguments
   name = "car_name"
   type = "string"            # string | integer | number | boolean
   description = "Case-insensitive substring match on the car's name."

   [[params]]
   name = "limit"
   type = "integer"
   description = "Maximum number of rows returned."
   default = 10
   minimum = 1
   maximum = 100

   [[output]]                 # optional: one entry per result column
   name = "car_name"
   type = "string"
   ```

3. Write parameters as `%(car_name)s` placeholders in the SQL. Do not build SQL from strings. Cast the first use of each placeholder, for example `%(limit)s::int`. Write a literal `%` as `%%`. The reserved `%(tz)s` placeholder binds `REPORT_TIMEZONE`.
4. Restart the server. The registry validates the pair and registers the tool. Run `teslamate-mcp list-tools` to confirm.

The startup validation rejects a query when a declared parameter is missing from the SQL, or when the SQL uses an undeclared parameter.

## Security

- `run_sql` runs in a `READ ONLY` transaction that always rolls back. Timeouts and a row cap apply.
- Connect with a `SELECT`-only PostgreSQL role, not TeslaMate's own `teslamate` user. TeslaMate's Compose makes that user a superuser, and a read-only transaction still lets a superuser read server files through `run_sql`. [SECURITY.md](SECURITY.md) has the `CREATE ROLE` snippet.
- The maps' basemap tiles are fetched from Esri by the viewer's browser, which reveals the rough area being viewed. `MAP_TILES=false` turns this off.
- The HTTP transport compares bearer tokens with a timing-safe function. Behind Cloudflare Access, it can instead verify the signed `Cf-Access-Jwt-Assertion` (signature, issuer, audience, expiry).
- Report vulnerabilities through [private security advisories](https://github.com/cobanov/teslamate-mcp/security/advisories/new). See [SECURITY.md](SECURITY.md).

## Development

```bash
uv sync                          # install with dev dependencies
uv run ruff check src tests      # lint
uv run ruff format src tests     # format
uv run pytest                    # database tests need Docker or TESLAMATE_TEST_DATABASE_URL (a scratch database)
```

## License

MIT — see [LICENSE](LICENSE).

TDQS

A3.6/5.0

Scored across 18 tools

Disambiguation4/5

Most tools have distinct purposes focusing on different aspects of Tesla vehicle data (charging, driving, battery, efficiency, locations, etc.), though some like 'get_battery_degradation_over_time' and 'get_battery_health_summary' could potentially overlap in scope, and 'get_daily_driving_patterns' and 'get_drive_summary_per_day' might be confused for similar daily analyses. Descriptions help clarify, but there is minor ambiguity in a few cases.

Naming Consistency5/5

All tool names follow a consistent 'get_' prefix with descriptive snake_case nouns, such as 'get_all_charging_sessions_summary' and 'get_current_car_status'. This uniform pattern makes the tool set predictable and easy to navigate, with no deviations in naming conventions.

Tool Count4/5

With 18 tools, the count is slightly high but reasonable for a comprehensive Tesla data analytics server, covering various metrics like charging, driving, battery, and efficiency. It might feel a bit heavy, but each tool appears to serve a specific purpose within the domain, avoiding redundancy.

Completeness5/5

The tool set provides extensive coverage for analyzing Tesla vehicle data, including real-time status, historical trends, charging patterns, driving habits, battery health, efficiency metrics, and anomaly detection. There are no obvious gaps; it supports a full lifecycle of data retrieval and analysis without dead ends for the stated purpose.

Maintenance

ActivityActive
ResponsivenessSlow