@cyanheads/openchargemap-mcp-server
Public Hosted Server: https://openchargemap.caseyjhand.com/mcp
Tools
Four tools across the find-and-detail surface — search, detail, offline ID resolution, and the community reliability layer:
Tool | Description |
| Find charging stations near a point or within a bounding box, filtered by connector, power, network, usage, status, and charge points. Coordinate-native. |
| Full record for one station by numeric OCM ID — every connection, operator, access rules, charge points, cost, media, and a computed reliability note. |
| Resolve connector/operator/usage/status/country names to the integer filter IDs |
| Community check-ins for one station alongside the registry status and last-verified date, so registry-vs-reality mismatch is visible. |
openchargemap_find_stations
The workhorse. Search the global registry by location, then narrow with filters.
Radius search (
latitude+longitude+distance, inKMorMiles) orboundingbox— exactly one mode per call. Aboundingboxsent alongside alatitudeor alongitudeis rejected rather than searched with the coordinate quietly droppedOptional country scope via ISO 3166-1 alpha-2
countrycode; global by default, no implicit countryFilters: connector type, minimum power (kW), operator/network, usage type, charge level, operational status, minimum charge points — all integer IDs, single or OR-matched arrays
Resolve a connector or network name to its filter ID with
openchargemap_lookup_referencefirst (e.g."CCS"→33)Each result carries title, address, distance, connections (type/power/current/count), operator, access rules, registry status, and
dateLastVerifiedmaxresultscaps the page (default 25, max 200), ordered by distance. A truncated page reportsnextOffset; pass it back asoffsetfor the next page. OCM has no offset parameter of its own, so paging runs over an over-fetched candidate page — reachable depth is 500 stations per search (offset0–499), andtotalCountis what the search retrieved rather than a registry-wide total (OCM publishes none). It is exact only when the candidate page came back short of its cap; otherwise it is a floor, and the notice says soLocal filters (
minchargepoints, and the drop of OCM's 0,0 coordinate sentinels) run over that whole candidate page, so a match ranked pastmaxresultsis not lost and a page emptied by filtering is reported as truncated, not as "no stations"Coordinate-native — does not geocode place names. Resolve a place like "Ballard, Seattle" to coordinates with a geocoding server (e.g. the
openstreetmapMCP server'sopenstreetmap_geocode) first, then pass them here
openchargemap_get_station
Full detail for one station by its numeric OCM ID (fetched with verbose=true).
Every connection: type, level, power, current, amperage, voltage, quantity
Operator and network, usage and access restrictions (pay-at-location, membership, access key), number of charge points
General comments, usage cost, data provider, submitted media, verification recency
Optional inline community check-ins with
includeCommentsComputes a plain-prose
reliabilityNotefrom observable facts (verification age, registry status, operational flag, fault-vs-positive check-in counts) — no synthetic score; omitted when status is fresh and uncontestedA status of "Temporarily Unavailable" or "Partly Operational (Mixed)" raises a caveat of its own — OCM flags both operational, so the flag alone would read as all-clear on a station the operator has said is down
Obtain an ID from
openchargemap_find_stations. UUID lookup is not supported by the OCM API.
openchargemap_lookup_reference
Resolve Open Charge Map reference data to the integer IDs the find_stations filters require — served from a bundled snapshot, so it makes no network call (offline, instant).
Categories:
connectiontypes,operators,usagetypes,statustypes,currenttypes,levels,countriesPass a
queryto resolve a name, title, code, or alias ("CCS","Tesla Supercharger","ChargePoint","Public - Pay At Location","France","FR") — case-insensitive, matched on title, formal name, and curated connector aliasesOmit the
queryto browse the whole category (up tolimit, max 100). Browsing and querying both page:totalCountis the full match count, and a truncated page reportsnextOffsetto pass back asoffset— every entry in a large category likeoperators(974) orcountries(250) is reachableReturns the matching
id(s) plus thefilterParamthey feed and the vintage of the data actually served —snapshotDatewith asourceofliveorbundled, so a fresh fetch is never mistaken for a freshly cut bundleAn optional startup refresh keeps the snapshot from drifting — see
OPENCHARGEMAP_REFERENCE_REFRESHbelow. When it succeeds,sourceisliveandsnapshotDateis the day it ran; when it is off or it failed,sourceisbundledand the date is the bundle's own
openchargemap_get_station_comments
Community check-ins for one station — the honest reliability signal beyond the operator-reported registry flag.
Returns user comments and fault reports with ratings, dates, and the recorded check-in outcome ("Charged Successfully", "Failed to Charge (Equipment Not Operational)", …), newest first (
maxresultscaps the page, max 100)One POI fetch carries every comment the station has, so
totalCountis exact andoffset(paired with thenextOffseta truncated page reports) reads the rest without a further upstream callEvery count states its population:
totalCommentsis the station's whole set, the rendered header reads2 of 6 comment(s)when a page is only part of it, andreliabilityNote's fault ratio is counted over the whole set so it does not move withmaxresultsThe check-in outcome is the charge-attempt result and drives fault detection — a failed charge counts even when it was filed as a plain comment
Surfaces the station's registry status, operational flag, and
dateLastVerifiedalongside the comments so you can flag mismatches like "listed operational, but recent check-ins report a fault"An empty result is not an error — a station with no check-ins returns
comments: []; absence of reports is not evidence the charger worksBacked by the POI fetch with
includecomments=true(OCM has no standalone comments endpoint)Obtain a station ID from
openchargemap_find_stations
Resource
Type | Name | Description |
Resource |
| Full station record (with community comments) by numeric OCM ID — the URI-addressable twin of |
All station data is also reachable via the tools. The station corpus (~200k locations, geo-scoped) is not exposed as a listable resource — discover stations with openchargemap_find_stations. Reference data is a resolve surface, not a stable-by-URI record, so it is served by openchargemap_lookup_reference rather than a resource.
Features
Built on @cyanheads/mcp-ts-core:
Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
Unified error handling — handlers throw, framework catches, classifies, and formats; typed error contracts with recovery hints
Pluggable auth:
none,jwt,oauthSwappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1Structured logging with optional OpenTelemetry tracing
STDIO and Streamable HTTP transports
Open Charge Map–specific:
Type-safe client for the OCM v3 POI API with retry and session-scoped result caching
Reference data (connectors, operators, usage/status types, countries) bundled as an offline snapshot — name→ID resolution needs no live
/referencedatacall, with an optional startup refresh to prevent driftCurated connector aliases (
CCS,NACS/Supercharger,J1772,Type 2,CHAdeMO) so the names agents actually use resolve to the right IDsGeocoding intentionally delegated — the server is coordinate-native and composes with any geocoding MCP server rather than rebuilding place-name lookup
Agent-friendly output:
Reliability surfaced as first-class signal —
status,statusTypeId,isOperational, anddateLastVerifiedon every station, plus a plain-prosereliabilityNotederived only from observable facts (no fabricated confidence score). Upstream values are passed through verbatim; where a status contradicts its own operational flag, the judgment lives in the note and the rendered text, never in the booleanHonest sparsity — heavily-omitted upstream fields are optional with "absence means unknown, not zero/false" descriptions; the server never invents data OCM didn't return. An explicit
falseis a fact and reaches the text output, not juststructuredContentCC BY 4.0 attribution on every tool response and in the server-level instructions, per the data license
Getting started
Public Hosted Instance
A public instance is available at https://openchargemap.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP, with this client config:
{
"mcpServers": {
"openchargemap-mcp-server": {
"type": "streamable-http",
"url": "https://openchargemap.caseyjhand.com/mcp"
}
}
}Local / self-hosted
Add the following to your MCP client configuration file. An Open Charge Map API key is required — see Prerequisites.
{
"mcpServers": {
"openchargemap-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/openchargemap-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"OPENCHARGEMAP_API_KEY": "your-api-key"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"openchargemap-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/openchargemap-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"OPENCHARGEMAP_API_KEY": "your-api-key"
}
}
}
}Or with Docker:
{
"mcpServers": {
"openchargemap-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "OPENCHARGEMAP_API_KEY=your-api-key",
"ghcr.io/cyanheads/openchargemap-mcp-server:latest"
]
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 OPENCHARGEMAP_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
Bun v1.3 or higher (or Node.js v24+).
An Open Charge Map API key — free instant signup: register an application at openchargemap.org. Sent as the
X-API-Keyheader on every request; the server fails fast at startup if it's unset.
Installation
Clone the repository:
git clone https://github.com/cyanheads/openchargemap-mcp-server.gitNavigate into the directory:
cd openchargemap-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set OPENCHARGEMAP_API_KEYConfiguration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
Variable | Description | Default |
| Required. Open Charge Map API key, sent as the | — |
| OCM API base URL. Override for a private mirror or testing. |
|
| When |
|
| Transport: |
|
| Port for the HTTP server. |
|
| Auth mode: |
|
| Log level (RFC 5424). |
|
| Directory for log files (Node.js only). |
|
| Storage backend. |
|
| Enable OpenTelemetry instrumentation (spans, metrics, completion logs). |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t openchargemap-mcp-server .
docker run --rm -e OPENCHARGEMAP_API_KEY=your-key -p 3010:3010 openchargemap-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/openchargemap-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Bundled Open Charge Map reference snapshot ( |
| Tool definitions ( |
| Resource definitions ( |
| OCM POI API client, response normalization, attribution, and the reliability-note helper. |
| Reference-data service — snapshot loading, lookup indices, curated aliases, optional live refresh. |
| Unit and integration tests mirroring |
Development guide
See AGENTS.md (and CLAUDE.md) for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools and resources in the
createApp()arraysWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Attribution and data license
Charging-station, connector, and operator data is sourced from Open Charge Map, the community-maintained global registry of EV charging locations, and is licensed under CC BY 4.0.
Station data © Open Charge Map contributors, licensed under CC BY 4.0 (openchargemap.org).
Attribution is mandatory: every tool response carries this attribution string, and the server restates it in its session-level instructions. Any downstream use of the data must credit Open Charge Map and its contributors. This server's own code is Apache-2.0 (below); the license terms above apply to the data, not the software.
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details. Open Charge Map data carries its own license; see Attribution and data license.