Tern EV-Charging Analytics
EV-charging site analytics: utilization, demand, peak saturation and corridor benchmarks.
What it can do
What data it sees
Do you need an account
An API key from the service settings is required
EV-charging site analytics: utilization, demand, peak saturation and corridor benchmarks.
How to connect
How to connect Tern EV-Charging Analytics to Claude
The server address to paste into Claude settings:
https://tern-mcp-537572042041.europe-west4.run.app/mcp
- Open Claude (claude.ai or the desktop app).
- Go to Settings → Connectors.
- Click Add custom connector.
- Paste the server address copied below into Remote MCP server URL and click Add.
- In a chat, click + → Connectors and switch the new connector on.
Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.
Authorization
After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.
How to connect Tern EV-Charging Analytics to ChatGPT
The server address to paste into ChatGPT settings:
https://tern-mcp-537572042041.europe-west4.run.app/mcp
- Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
- Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
- Open Settings → Connectors and click Create.
- Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
- Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
- In a new chat open + → Apps/Connectors and enable the connector.
OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.
Authorization
On first use ChatGPT opens the service's sign-in window. Sign in and approve access.
How to connect Tern EV-Charging Analytics to Cursor
The server address to paste into Cursor settings:
https://tern-mcp-537572042041.europe-west4.run.app/mcp
Fastest: click Open in Cursor below and confirm the prompt.
Manually:
- In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
- Paste the JSON copied below into
~/.cursor/mcp.json(per project:.cursor/mcp.jsonin the repo root). - Save the file. The server appears in the MCP list; authorize it there if asked.
Authorization
If the service needs sign-in, an authorize button appears next to the server in the MCP list.
Server tool list (26)
Raw names from tools/list. Only developers need these.
| search_charging_stations | Find charging stations in one country by structured filter — id, title, address, operator. The reply is the API payload inline: station ids, titles, addresses and operators, which is what the claim tools take as site_id and station_id. |
| nearby_charging_stations | List the charging stations within a radius of a point, nearest first. The radius is in kilometres and at most 10; the reply is the API payload inline. This is how a site's competitors are found before any claim is read. |
| list_evses | List the individual EVSEs (connectors) of up to 20 stations, with their capacity in kW. Stall counts and capacity bands come from here rather than from a station record. |
| aggregate_charging_utilization | Station-level utilization aggregated over a GeoJSON area and a date window of at most 92 days, optionally filtered to a capacity band (default 150 kW = HPC; pass 100 for fast charging). The reply is one row per station, so it reads an area without pulling any station's daily rows. |
| aggregate_evse_utilization | EVSE-level utilization aggregated over a GeoJSON area and a date window of at most 92 days, optionally filtered to a capacity band. The per-connector counterpart of aggregate_charging_utilization. |
| area_benchmark | How hard is each EVSE at this site working compared with the area around it? Reads the area_benchmark claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| avg_session_minutes | How long does a session at this site actually last? Reads the avg_session_minutes claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| busy_evse_hours | How many EVSE-hours at this station carried charging on the drilled day, and how many of its EVSEs were charging at once in the busiest hour? Reads the busy_evse_hours claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| corridor_benchmark | How does this site's demand per EVSE-day compare with the corridor drawn around it? Reads the corridor_benchmark claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| corridor_comparison | How does this site compare with the named stations around it — distance, EVSE count, max kW, and sessions per EVSE-day each? Reads the corridor_comparison claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| daily_spike | Which days ran far above this site's normal, and by how much? Reads the daily_spike claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| day_vs_similar | Was one particular day unusual for the day of week it fell on? Reads the day_vs_similar claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| demand_share_multiple | How much of the area's demand does this one site take, said as a multiple of the mean station? Reads the demand_share_multiple claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| estate_coverage | Which estate sites reported on every day of the window, which are partial or young, and how full is the window's final day? Reads the estate_coverage claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| estate_monthly_series | Did demand per EVSE across the estate move month over month, and did each focus site move with it? Reads the estate_monthly_series claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| hourly_spike | When in the day does demand at this site peak? Reads the hourly_spike claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| monthly_sessions_per_stall_day | Is demand per EVSE rising or falling month over month? Reads the monthly_sessions_per_stall_day claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| peak_hour_co_saturation | When the site reaches its busiest hour of the day, do the stations around it reach their own busiest hours at the same time? Reads the peak_hour_co_saturation claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| poi_context | What kind of place is this site sitting in? Reads the poi_context claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. places carries the rows this claim reads, because they are transcribed by the caller rather than fetched. |
| rank_gap | Where does this site sit in its estate's ranking, and how far is it from the estate median and the estate leader? Reads the rank_gap claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| road_context | What road does this site sit on, and what class is it? Reads the road_context claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| sessions_per_stall_day | How hard is each EVSE at this site working per day? Reads the sessions_per_stall_day claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| site_profile | What is this site — how many EVSEs, and how close is the nearest station? Reads the site_profile claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| site_ranking | How do the estate's sites rank on sessions per EVSE-day over one window, and how far is the leader above the estate median? Reads the site_ranking claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| total_charging_minutes | How many minutes of charging did this site actually deliver over the window? Reads the total_charging_minutes claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |
| total_sessions | How much charging demand did this site see over the window? Reads the total_sessions claim for one site and returns only its result: the status the claim decided from its own observations (computed or insufficient), the typed result, and the display a deliverable would print. The observations behind it are pulled into a scratch directory that is deleted before the call returns, so the rows never enter the conversation and nothing is left on the server to read back later — this call is the whole reading. |