bikerouter-mcp
# bikerouter-mcp
An [MCP](https://modelcontextprotocol.io) server that plans **bike routes** with
[bikerouter.de](https://bikerouter.de) — the BRouter-Web frontend by Norbert Renner on top of the
[BRouter](https://github.com/abrensch/brouter) engine by Arndt Brenschede, routing on OpenStreetMap data.
Point any MCP client (Claude Code, Claude Desktop, …) at it and ask things like
*"plan a gravel route from Cologne to Bonn along the Rhine and give me the GPX"*.
## What it can do
| Tool | Purpose |
| --- | --- |
| `plan_route` | Route through 2+ waypoints (coordinates or place names) and return distance, ascent/descent, estimated riding time, surface and way-type breakdown, turn instructions, a shareable bikerouter.de link, and optionally a saved GPX/GeoJSON/KML/CSV file. |
| `compare_profiles` | Route the same waypoints with several profiles (e.g. `trekking` vs `gravel` vs `fastbike`) and compare distance, time, climb and paved share side by side. |
| `geocode_place` | Turn a place name, address or POI into coordinates (via Photon, the geocoder bikerouter.de uses). |
| `list_profiles` | List the known routing profiles, optionally verifying which ones the routing host actually serves. |
| `get_profile_options` | Read a profile's `.brf` source and list the parameters it exposes (`avoid_unsafe`, `allow_ferries`, `totalMass`, `bikerPower`, …) for use with `plan_route`'s `profile_options`. |
Sample `plan_route` output:
```
**Route** (profile: `trekking`)
Alexanderplatz, Berlin → Brandenburger Tor, Berlin
- Distance: **5.2 km**
- Estimated riding time: **20 min** (⌀ 15.6 km/h, from the profile's physics model)
- Ascent: **80 m** / descent: **60 m**
- Elevation: 35–55 m (start 35 m, end 55 m)
- Surfaces: asphalt 76.9% (4 km), gravel 23.1% (1.2 km)
- Way types: cycleway 38.5% (2 km), residential 38.5% (2 km), track 23.1% (1.2 km)
Open in bikerouter.de: https://bikerouter.de/#map=14/52.518154/13.396327/standard&lonlats=…&profile=trekking
```
## Install
```bash
cd bikerouter-mcp
npm install # also builds via the prepare script
npm test
```
Register it with Claude Code:
```bash
claude mcp add bikerouter -- node /absolute/path/to/bikerouter-mcp/dist/index.js
```
or add it to `claude_desktop_config.json` / any other MCP client:
```json
{
"mcpServers": {
"bikerouter": {
"command": "node",
"args": ["/absolute/path/to/bikerouter-mcp/dist/index.js"]
}
}
}
```
No API key is needed — the service is free and unauthenticated.
## Configuration
All optional, set as environment variables:
| Variable | Default | Meaning |
| --- | --- | --- |
| `BIKEROUTER_HOST` | `https://brouter.de` | Host serving the BRouter `/brouter` routing endpoint. `bikerouter.de` is only the web frontend and 404s here, so this defaults to `brouter.de`. Point it at `http://localhost:17777` to use your own BRouter server. |
| `BIKEROUTER_WEB_URL` | `https://bikerouter.de` | Web UI used for the shareable map links. |
| `BIKEROUTER_PROFILES_URL` | `<host>/brouter/profiles2/`, then `<host>/profiles/`, then `<web>/profiles/` | Where `.brf` profile sources live (used by `get_profile_options`). |
| `BIKEROUTER_GEOCODER_URL` | `https://photon.komoot.io/api` | Photon-compatible geocoder. |
| `BIKEROUTER_DEFAULT_PROFILE` | `trekking` | Profile used when the caller does not name one. |
| `BIKEROUTER_TIMEOUT_MS` | `60000` | HTTP timeout. Long routes can take a while. |
| `BIKEROUTER_USER_AGENT` | `bikerouter-mcp/0.1.0` | Sent with every request. |
## How it maps onto the BRouter API
Requests are plain `GET /brouter` calls, exactly as documented in BRouter's
`ServerHandler.java`:
```
/brouter?lonlats=lon,lat|lon,lat&nogos=lon,lat,radius,weight|…&profile=trekking
&alternativeidx=0&format=geojson&timode=1&straight=0&profile:avoid_unsafe=true
```
- `waypoints` → `lonlats` (place names are geocoded first, biased towards the previous waypoint)
- `avoid_areas` → `nogos` (omit `weight` for a hard no-go)
- `straight_from` → `straight` (beeline legs)
- `profile_options` → `profile:NAME=VALUE`
- `alternative_index` → `alternativeidx` (1–3 for alternative routes)
- `turn_instructions` → `timode` (voice hints in the GeoJSON response)
The GeoJSON response is summarised locally: track length, `filtered ascend`,
`total-time` and `total-energy` come from the track properties, descent is
derived from the filtered ascent and the net height change, and the surface /
way-type / smoothness breakdown is aggregated from the `messages` table
(`WayTags` weighted by `Distance`).
## Limitations worth knowing
- **Routing quality is OpenStreetMap quality.** Missing surface or access tags produce odd detours; always sanity-check a route on the returned map link before riding it.
- **BRouter has no street names**, so turn instructions read "in 1.2 km turn right" without a road name. That is a property of the engine, not of this server.
- **Estimated riding time** comes from the profile's physics model (rider mass, power, drag). Override `totalMass`, `bikerPower`, `maxSpeed` via `profile_options` for a realistic figure.
- **Elevation** needs SRTM coverage; above ~60°N/S the track comes back without elevations and the summary says so.
- The public service is a **free, community-run instance** — keep request volume modest, or run your own BRouter server and set `BIKEROUTER_HOST`.
- Profiles differ per server. `list_profiles` ships a catalog of the stock BRouter/BRouter-Web profiles; pass `verify: true` to check what a given host really serves.
## Tests
`npm test` builds the server and runs `node --test`:
- unit tests for URL building, waypoint validation, `.brf` parameter parsing and the GeoJSON summariser
- an end-to-end test that speaks MCP over stdio to the real server binary against a stub BRouter host, covering routing, GPX export, profile listing and BRouter's plain-text error responses
The tests never touch the public service, so they run offline.
## Credits and licence
Server code: MIT. It is a client only — routing is done by
[BRouter](https://github.com/abrensch/brouter) (MIT) through
[BRouter-Web](https://github.com/nrenner/brouter-web) / bikerouter.de, on map data
© [OpenStreetMap contributors](https://www.openstreetmap.org/copyright) (ODbL).
TDQS
Scored across 5 tools
The five tools are fairly distinct: plan_route is the core routing function, compare_profiles explicitly compares routing results across profiles, geocode_place converts names to coordinates, and list_profiles/get_profile_options are clearly about profile metadata. Minor confusion could arise between list and get_profile, but their scope is differentiated by listing and options-fetching.
All tool names follow a consistent verb_noun pattern in lowercase with underscores: plan/compare/geocode/list/get + route/profiles/place. Every verb is specific and every noun clearly reflects the resource. There is no mixing of naming styles or tense conventions.
The server provides exactly five tools for a domain that is naturally narrow: compose a route, compare routing outcomes, geocode a waypoint, inspect available profiles, and retrieve profile tuning details. This is a well-scoped set for a routing assistant without feature bloat.
The tool set covers the core lifecycle of bike route planning smoothly: geocoding inputs, planning a route, selecting and comparing routing styles. The main gap is the lack of a save/load route history or a user-specific profile creation tool, but these are optional conveniences, not critical. There are no major dead-ends in the existing workflow.